promise 鏈在錯誤(error)處理中十分強大。當壹個 promise 被 reject 時,控制權將移交至最近的 rejection 處理程序。這在實際開發中非常方便。
例如,下面代碼中所 fetch
的 URL 是錯的(沒有這個網站),.catch
對這個 error 進行了處理:
fetch('https://no-such-server.blabla') // reject .then(response => response.json()) .catch(err => alert(err)) // TypeError: Failed to fetch(這裏的文字可能有所不同)
正如妳所看到的,.catch
不必是立即的。它可能在壹個或多個 .then
之後出現。
或者,可能該網站壹切正常,但響應不是有效的 JSON。捕獲所有 error 的最簡單的方法是,將 .catch
附加到鏈的末尾:
fetch('https://javascript.info/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
根本不會被觸發。但是如果上述任意壹個 promise rejected(網絡問題或者無效的 json 或其他),.catch
就會捕獲它。
promise 的執行者(executor)和 promise 的處理程序周圍有壹個“隱式的 try..catch
”。如果發生異常,它就會被捕獲,並被視爲 rejection 進行處理。
例如,下面這段代碼:
new Promise((resolve, reject) => { throw new Error("Whoops!"); }).catch(alert); // Error: Whoops!
……與下面這段代碼工作上完全相同:
new Promise((resolve, reject) => { reject(new Error("Whoops!")); }).catch(alert); // Error: Whoops!
在 executor 周圍的“隱式 try..catch
”自動捕獲了 error,並將其變爲 rejected promise。
這不僅僅發生在 executor 函數中,同樣也發生在其處理程序中。如果我們在 .then
處理程序中 throw
,這意味著 promise rejected,因此控制權移交至最近的 error 處理程序。
這是壹個例子:
new Promise((resolve, reject) => { resolve("ok"); }).then((result) => { throw new Error("Whoops!"); // reject 這個 promise }).catch(alert); // Error: Whoops!
對于所有的 error 都會發生這種情況,而不僅僅是由 throw
語句導致的這些 error。例如,壹個編程錯誤:
new Promise((resolve, reject) => { resolve("ok"); }).then((result) => { blabla(); // 沒有這個函數 }).catch(alert); // ReferenceError: blabla is not defined
最後的 .catch
不僅會捕獲顯式的 rejection,還會捕獲它上面的處理程序中意外出現的 error。
正如我們已經注意到的,鏈尾端的 .catch
的表現有點像 try..catch
。我們可能有許多個 .then
處理程序,然後在尾端使用壹個 .catch
處理上面的所有 error。
在常規的 try..catch
中,我們可以分析 error,如果我們無法處理它,可以將其再次抛出。對于 promise 來說,這也是可以的。
如果我們在 .catch
中 throw
,那麽控制權就會被移交到下壹個最近的 error 處理程序。如果我們處理該 error 並正常完成,那麽它將繼續到最近的成功的 .then
處理程序。
在下面這個例子中,.catch
成功處理了 error:
// 執行流: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
的另壹種情況。(*)
行的處理程序捕獲了 error,但無法處理它(例如,它只知道如何處理 URIError
),所以它將其再次抛出:
// 執行流:catch -> catch new Promise((resolve, reject) => { throw new Error("Whoops!"); }).catch(function(error) { // (*) if (error instanceof URIError) { // 處理它 } else { alert("Can't handle such error"); throw error; // 再次抛出此 error 或另外壹個 error,執行將跳轉至下壹個 catch } }).then(function() { /* 不在這裏運行 */ }).catch(error => { // (**) alert(`The unknown error has occurred: ${error}`); // 不會返回任何內容 => 執行正常進行 });
執行從第壹個 .catch
(*)
沿著鏈跳轉至下壹個 (**)
。
當壹個 error 沒有被處理會發生什麽?例如,我們忘了在鏈的尾端附加 .catch
,像這樣:
new Promise(function() { noSuchFunction(); // 這裏出現 error(沒有這個函數) }) .then(() => { // 壹個或多個成功的 promise 處理程序 }); // 尾端沒有 .catch!
如果出現 error,promise 的狀態將變爲 “rejected”,然後執行應該跳轉至最近的 rejection 處理程序。但上面這個例子中並沒有這樣的處理程序。因此 error 會“卡住”。沒有代碼來處理它。
在實際開發中,就像代碼中常規的未處理的 error 壹樣,這意味著某些東西出了問題。
當發生壹個常規的 error 並且未被 try..catch
捕獲時會發生什麽?腳本死了,並在控制台中留下了壹個信息。對于在 promise 中未被處理的 rejection,也會發生類似的事。
JavaScript 引擎會跟蹤此類 rejection,在這種情況下會生成壹個全局的 error。如果妳運行上面這個代碼,妳可以在控制台中看到。
在浏覽器中,我們可以使用 unhandledrejection
事件來捕獲這類 error:
window.addEventListener('unhandledrejection', function(event) { // 這個事件對象有兩個特殊的屬性: alert(event.promise); // [object Promise] —— 生成該全局 error 的 promise alert(event.reason); // Error: Whoops! —— 未處理的 error 對象 }); new Promise(function() { throw new Error("Whoops!"); }); // 沒有用來處理 error 的 catch
這個事件是 HTML 標准 的壹部分。
如果出現了壹個 error,並且在這沒有 .catch
,那麽 unhandledrejection
處理程序就會被觸發,並獲取具有 error 相關信息的 event
對象,所以我們就能做壹些後續處理了。
通常此類 error 是無法恢複的,所以我們最好的解決方案是將問題告知用戶,並且可以將事件報告給服務器。
在 Node.js 等非浏覽器環境中,有其他用于跟蹤未處理的 error 的方法。
.catch
處理 promise 中的各種 error:在 reject()
調用中的,或者在處理程序中抛出的 error。
如果給定 .then
的第二個參數(即 error 處理程序),那麽 .then
也會以相同的方式捕獲 error。
我們應該將 .catch
准確地放到我們想要處理 error,並知道如何處理這些 error 的地方。處理程序應該分析 error(可以自定義 error 類來幫助分析)並再次抛出未知的 error(它們可能是編程錯誤)。
如果沒有辦法從 error 中恢複,不使用 .catch
也可以。
在任何情況下我們都應該有 unhandledrejection
事件處理程序(用于浏覽器,以及其他環境的模擬),以跟蹤未處理的 error 並告知用戶(可能還有我們的服務器)有關信息,以使我們的應用程序永遠不會“死掉”。
妳怎麽看?.catch
會被觸發麽?解釋妳的答案。
new Promise(function(resolve, reject) { setTimeout(() => { throw new Error("Whoops!"); }, 1000); }).catch(alert);
答案是:不,它不會被觸發:
new Promise(function(resolve, reject) { setTimeout(() => { throw new Error("Whoops!"); }, 1000); }).catch(alert);
正如本章所講,函數代碼周圍有個“隱式的 try..catch
”。所以,所有同步錯誤都會得到處理。
但是這裏的錯誤並不是在 executor 運行時生成的,而是在稍後生成的。因此,promise 無法處理它。