承諾鏈在錯誤處理方面非常棒。當承諾遭到拒絕時,控制權會跳到最近的拒絕處理常式。這在實務上非常方便。
例如,在以下程式碼中,fetch
的 URL 錯誤(沒有此網站),而 .catch
處理錯誤
fetch('https://no-such-server.blabla') // rejects
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)
如你所見,.catch
不必馬上執行。它可能出現在一個或多個 .then
之後。
或者,網站一切正常,但回應不是有效的 JSON。捕捉所有錯誤最簡單的方法是將 .catch
附加在鏈的結尾
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise((resolve, reject) => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
.catch(error => alert(error.message));
通常,此類 .catch
根本不會觸發。但如果上述任何承諾遭到拒絕(網路問題或無效的 json 或其他任何問題),它就會捕捉到它。
隱含的 try…catch
承諾執行器和承諾處理常式的程式碼周圍有一個「隱形的 try..catch
」。如果發生例外情況,它會被捕捉並視為拒絕。
例如,此程式碼
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!
…與以下程式碼完全相同
new Promise((resolve, reject) => {
reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!
執行器周圍的「隱形 try..catch
」會自動捕捉錯誤並將其轉換為拒絕的承諾。
這不僅發生在執行器函式中,也發生在它的處理常式中。如果我們在 .then
處理常式內 throw
,表示拒絕的承諾,因此控制權會跳到最近的錯誤處理常式。
以下是範例
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
throw new Error("Whoops!"); // rejects the promise
}).catch(alert); // Error: Whoops!
這會發生在所有錯誤中,而不仅仅是 throw
陳述式造成的錯誤。例如,程式設計錯誤
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
blabla(); // no such function
}).catch(alert); // ReferenceError: blabla is not defined
最後一個 .catch
不僅會捕捉明確的拒絕,還會捕捉上述處理常式中的意外錯誤。
重新擲回
正如我們已經注意到的,鏈末的 .catch
類似於 try..catch
。我們可以擁有任意數量的 .then
處理常式,然後在最後使用單一的 .catch
來處理所有處理常式中的錯誤。
在常規的 try..catch
中,我們可以分析錯誤,如果無法處理,則可能重新擲回錯誤。承諾也可以執行相同的操作。
如果我們在 .catch
內 throw
,則控制權會轉到下一個最近的錯誤處理常式。如果我們處理錯誤並正常完成,則它會繼續到下一個最近的成功的 .then
處理常式。
在以下範例中,.catch
成功處理錯誤
// the execution: catch -> then
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) {
alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
這裡的 .catch
區塊正常完成。因此,會呼叫下一個成功的 .then
處理常式。
在以下範例中,我們看到 .catch
的另一種情況。處理常式 (*)
捕捉錯誤,但無法處理它(例如,它只知道如何處理 URIError
),因此再次擲回錯誤
// the execution: catch -> catch
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) { // (*)
if (error instanceof URIError) {
// handle it
} else {
alert("Can't handle such error");
throw error; // throwing this or another error jumps to the next catch
}
}).then(function() {
/* doesn't run here */
}).catch(error => { // (**)
alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way
});
執行從第一個 .catch
(*)
跳到鏈中的下一個 (**)
。
未處理的拒絕
如果錯誤未處理,會發生什麼情況?例如,我們忘記在鏈末附加 .catch
,如下所示
new Promise(function() {
noSuchFunction(); // Error here (no such function)
})
.then(() => {
// successful promise handlers, one or more
}); // without .catch at the end!
如果發生錯誤,承諾會被拒絕,執行應該跳到最近的拒絕處理常式。但沒有拒絕處理常式。因此,錯誤會「卡住」。沒有程式碼可以處理它。
實際上,就像程式碼中未處理的常規錯誤一樣,這表示某處發生了嚴重的問題。
當發生常規錯誤且未被 try..catch
捕捉時,會發生什麼情況?腳本會在主控台中顯示訊息並結束。未處理的承諾拒絕也會發生類似的情況。
JavaScript 引擎會追蹤此類拒絕,並在這種情況下產生全域性錯誤。如果您執行上述範例,可以在主控台中看到它。
在瀏覽器中,我們可以使用事件 unhandledrejection
捕捉此類錯誤
window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // [object Promise] - the promise that generated the error
alert(event.reason); // Error: Whoops! - the unhandled error object
});
new Promise(function() {
throw new Error("Whoops!");
}); // no catch to handle the error
這個事件是 HTML 標準 的一部分。
如果發生錯誤,而且沒有 .catch
,則會觸發 unhandledrejection
處理常式,並取得包含錯誤資訊的 event
物件,以便我們可以採取某些措施。
通常此類錯誤無法復原,因此我們最好的方法是告知使用者問題,並可能將事件報告給伺服器。
在非瀏覽器環境(例如 Node.js)中,還有其他方法可以追蹤未處理的錯誤。
摘要
.catch
處理各種承諾中的錯誤:不論是reject()
呼叫,或是在處理常式中引發的錯誤。- 如果提供了第二個引數(即錯誤處理常式),則
.then
也會以相同的方式捕捉錯誤。 - 我們應該將
.catch
精確地放置在我們想要處理錯誤並知道如何處理它們的地方。處理常式應該分析錯誤(自訂錯誤類別有幫助),並重新引發未知錯誤(也許它們是程式設計錯誤)。 - 如果無法從錯誤中復原,則不使用
.catch
也可以。 - 無論如何,我們都應該有
unhandledrejection
事件處理常式(適用於瀏覽器,以及其他環境的類比)來追蹤未處理的錯誤,並通知使用者(以及我們的伺服器),以便我們的應用程式永遠不會「突然死亡」。
留言
<code>
標籤,若要插入多行,請將它們包覆在<pre>
標籤中,若要插入 10 行以上,請使用沙盒(plnkr、jsbin、codepen…)