# Promises 鏈
我們回顧一下 info:callbacks 章節提及的問題。
* 假如我們有一系列異步任務會被依次完成。例如:加載腳本。
* 如何編寫合適的代碼?
Promises 提供了幾種方案來解決這個問題。
本章節中我們來講解 promise 鏈。
它看起來就像這樣:
~~~js
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
~~~
它的理念是把 result 傳入`.then`的處理程序鏈。
運行流程如下:
1. 初始 promise 1 秒后 resolve`(*)`,
2. 然后`.then`方法被調用`(**)`。
3. 它返回的值被傳入下一個`.then`的處理程序`(***)`
4. ……依此類推。
隨著 result 在處理程序鏈中傳遞,我們會看到`alert`依次顯示:`1`\->`2`\->`4`。
[](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/promise-then-chain.png)
之所以這么運行,是因為`promise.then`返回了一個 promise,所以我們可以用它調用下一個`.then`。
當控制函數返回一個值時,它會變成當前 promise 的 result,所以會用它調用下一個`.then`。
為了把這些話講更清楚,我們看一下鏈的開頭:
~~~js
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result);
return result * 2; // <-- (1)
}) // <-- (2)
// .then…
~~~
`.then`返回的值是一個 promise,這是為什么我們可以在`(2)`處添加另一個`.then`。在`(1)`處返回值時,當前 promise 變成 resolved,然后下一個處理程序使用這個返回值運行。
與鏈式調用不同,理論上我們也能添加許多`.then`到一個 promise 上,就像這樣:
~~~js
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
~~~
……但這是一個完全不同的東西,看這張圖(對比上面的鏈式調用):
[](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/promise-then-many.png)
在同一個 promise 上的所有`.then`會得到相同的結果 —— 該 promise 的 result。所以,以上代碼中所有`alert`會顯示相同的內容:`1`。它們之間沒有 result 的傳遞。
實際上我們極少遇到一個 promise 需要多處理程序,而是更經常地使用鏈式調用。
## [](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/article.md#%E8%BF%94%E5%9B%9E-promises)返回 promises
正常來說,`.then`處理程序返回的值會立即傳入下一個處理程序。但是有一個例外。
如果返回的值是一個 promise,那么直到它結束之前,下一步執行會一直被暫停。在結束之后,該 promise 的結果會傳遞給下一個`.then`處理程序。
例如:
~~~js
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
*!*
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
*/!*
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
~~~
這里第一個`.then`顯示`1`并在`(*)`行返回`new Promise(…)`,一秒之后它會 resolve 掉,然后 result(`resolve`的參數,在這里它是`result*2`)被傳遞給位于`(**)`行的第二個`.then`。它會顯示`2`,而且執行相同的動作。
所以輸出還是 1 -> 2 -> 4,但是現在每次`alert`調用之間會有 1 秒鐘的延遲。
返回 promises 允許我們建立異步動作鏈。
## [](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/article.md#%E7%A4%BA%E4%BE%8Bloadscript)示例:loadScript
讓我們使用這個功能用`loadScript`依次按順序加載腳本:
~~~js
loadScript("/article/promise-chaining/one.js")
.then(function(script) {
return loadScript("/article/promise-chaining/two.js");
})
.then(function(script) {
return loadScript("/article/promise-chaining/three.js");
})
.then(function(script) {
// 使用腳本里聲明的函數來表明它們的確被加載了
one();
two();
three();
});
~~~
這里每個`loadScript`調用返回一個 promise,并且在它 resolve 時運行下一個`.then`。 然后它開始加載下一個腳本。所以腳本是依次被加載的。
我們可以在鏈中添加更多的異步動作。請注意代碼仍然“扁平”,它向下增長,而不是向右。沒有“死亡金字塔”的跡象。
請注意理論上也可以在每一個 promise 后直接寫`.then`,而不是返回它們,就像這樣:
~~~js
loadScript("/article/promise-chaining/one.js").then(function(script1) {
loadScript("/article/promise-chaining/two.js").then(function(script2) {
loadScript("/article/promise-chaining/three.js").then(function(script3) {
// 這個函數可以訪問 script1、script2 和 script3 變量
one();
two();
three();
});
});
});
~~~
這段代碼做了一樣的事情:順序加載 3 個腳本。但是它“向右增長”。所以和使用回調函數一樣,我們會碰到相同的問題。要使用鏈式風格(`.then`中返回 promise)來避免這個問題。
有時直接寫`.then`是沒問題的,因為嵌套函數可以訪問外部作用域(這里大多數嵌套回調函數有權利訪問所有的變量`scriptX`),但這是一個例外而不算是規則。
~~~
確切地說,`.then` 可以返回任意的 “thenable” 對象,并且會被當做一個 promise 來對待。
“thenable” 對象指擁有 `.then` 方法的任意對象。
第三方庫能實現它們自己的 “可兼容 promise” 對象就是這種理念。他們可以擴展方法集,不過會保證與原生 promise 兼容,因為他們實現了 `.then` 方法。
這里是一個 thenable 對象的示例:
```js run
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// 1 秒后用 this.num*2 來 resolve
setTimeout(() => resolve(this.num * 2), 1000); // (**)
}
}
new Promise(resolve => resolve(1))
.then(result => {
return new Thenable(result); // (*)
})
.then(alert); // 1000 ms 后顯示 2
```
JavaScript 在 `(*)` 行檢查 `.then` 處理程序返回的對象:如果它有一個名為 `then` 的可調用方法,那么它會調用該方法并提供原生函數 `resolve`,`reject 作為參數(類似于 executor)并在它被調用前一直等待。上面的例子中 `resolve(2)` 1 秒后被調用 `(**)`。然后 result 會延鏈向下傳遞。
這個功能允許整合定制對象和 promise 鏈,不用從 `Promise` 繼承。
~~~
## [](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/article.md#%E6%9B%B4%E5%A4%8D%E6%9D%82%E7%9A%84%E7%A4%BA%E4%BE%8Bfetch)更復雜的示例:fetch
在前端編程中,promise 經常被用來網絡請求,就讓我們再看一個關于這點展開的示例。
我們將使用 fetch 方法從遠程服務器加載用戶信息。該方法十分復雜,它有很多可選參數,但是基本用法十分簡單:
~~~js
let promise = fetch(url);
~~~
它發送網絡請求到`url`并返回一個 promise。當遠程服務器返回響應頭(注意不是**全部響應加載完成**)時,該 promise 用一個`response`來 resolve 掉。
為了讀取全部的響應,我們應該調用方法`response.text()`:當全部文字內容從遠程服務器上下載后,它會返回一個 resolved 狀態的 promise,同時該文字會作為 result。
下面代碼向`user.json`發送請求并從服務器加載文字。
~~~js
fetch('/article/promise-chaining/user.json')
// 當遠程服務器開始響應時,下面的 .then 執行
.then(function(response) {
// 當結束下載時,response.text() 會返回一個新的 resolved promise,該 promise 擁有全部響應文字
return response.text();
})
.then(function(text) {
// ...這是遠程文件內容
alert(text); // {"name": "iliakan", isAdmin: true}
});
~~~
其實還有一個方法,`response.json()`會讀取遠程數據并把它解析成 JSON。我們的示例中用這個方法要更方便,所以讓我們替換成此方法。
為了簡潔,我們也使用箭頭函數:
~~~js
// 同上,但是使用 response.json() 把遠程內容解析為 JSON
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name)); // iliakan
~~~
現在我們用加載好的用戶信息搞點事情。
例如,我們可以多發一個請求到 github,加載用戶信息并顯示頭像:
~~~js
// 發一個 user.json 請求
fetch('/article/promise-chaining/user.json')
// 作為 json 加載
.then(response => response.json())
// 發一個請求到 github
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// 響應作為 json 加載
.then(response => response.json())
// 顯示頭像圖片(githubUser.avatar_url)3 秒(也可以加上動畫效果)
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => img.remove(), 3000); // (*)
});
~~~
這段代碼可以工作,具體細節請看注釋,它有良好的自我描述。但是,有一個潛在的問題,一個新手使用 promise 的典型問題。
請看`(*)`行:我們如何能在頭像結束顯示并在移除**之后**做點什么?例如,我們想顯示一個可以編輯用戶,或者別的表單。就目前而言是做不到的。
為了使鏈可擴展,我們需要在頭像結束顯示時返回一個 resolved 狀態的 promise。
就像這樣:
~~~js
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(function(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);
}))
// triggers after 3 seconds
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
~~~
現在,在`setTimeout`后運行`img.remove()`,然后調用`resolve(githubUser)`,這樣鏈中的控制流程走到下一個`.then`并傳入用戶數據。
作為一個規律,一個異步動作應該永遠返回一個 promise。
這讓規劃下一步動作成為可能。雖然現在我們沒打算擴展鏈,我們可能在日后需要它。
最終,我們可以把代碼分割成幾個可復用的函數:
~~~js
function loadJson(url) {
return fetch(url)
.then(response => response.json());
}
function loadGithubUser(name) {
return fetch(`https://api.github.com/users/${name}`)
.then(response => response.json());
}
function showAvatar(githubUser) {
return new Promise(function(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);
});
}
// 使用它們
loadJson('/article/promise-chaining/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
// ...
~~~
## [](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/article.md#%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86)錯誤處理
異步動作可能會失敗:如果出現錯誤,相應的 promise 會變成 rejected。例如,如果遠程服務器不可用`fetch`會失敗。我們可以使用`.catch`來處理錯誤(rejections)。
promise 鏈在這方面做的很棒。當一個 promise reject 時,代碼控制流程跳到鏈中最近的 rejection 處理程序。這在實踐中非常方便。
例如,下面代碼中的 URL 是錯的(沒有這個服務器)并且使用`.catch`處理錯誤:
~~~js
*!*
fetch('https://no-such-server.blabla') // rejects
*/!*
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)
~~~
或者,服務器的一切都很好,但響應不是有效的 JSON:
~~~js
fetch('/') // fetch 現在運行良好,服務器成功響應
*!*
.then(response => response.json()) // rejects:頁面是 HTML,而不是有效的 json
*/!*
.catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0
~~~
在下面的示例中,我們將附加`.catch`來處理在頭像 —— 加載 —— 顯示鏈中所有的錯誤:
~~~js
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(function(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 reject,那么它就會執行。
## [](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/article.md#%E9%9A%90%E5%BC%8F-trycatch)隱式 try..catch
executor 和 promise 處理程序代碼周圍有一個 “隱藏的`try..catch`”。如果錯誤發生,它會捕捉異常并當做一個 rejection 來對待。
例如這段代碼:
~~~js
new Promise(function(resolve, reject) {
*!*
throw new Error("Whoops!");
*/!*
}).catch(alert); // Error: Whoops!
~~~
...和這里工作方式相同:
~~~js
new Promise(function(resolve, reject) {
*!*
reject(new Error("Whoops!"));
*/!*
}).catch(alert); // Error: Whoops!
~~~
執行代碼周圍“隱藏的`try..catch`”自動捕獲錯誤并把它作為一個 rejection 對待。
這不止是在 executor 中,處理程序也有。如果我們在`.then`處理程序中`throw`,就意味著返回了一個 rejected promise,所以代碼控制流程會跳到最近的錯誤處理程序。
這有一個例子:
~~~js
new Promise(function(resolve, reject) {
resolve("ok");
}).then(function(result) {
*!*
throw new Error("Whoops!"); // rejects the promise
*/!*
}).catch(alert); // Error: Whoops!
~~~
這不僅適用于`throw`,而且適用于任何錯誤,包括編程錯誤:
~~~js
new Promise(function(resolve, reject) {
resolve("ok");
}).then(function(result) {
*!*
blabla(); // 沒有此方法
*/!*
}).catch(alert); // ReferenceError: blabla is not defined
~~~
作為一個副作用,最終`.catch`不僅會捕獲明確的 rejections,也會捕獲在上面的處理程序中偶爾出現的錯誤。
## [](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/article.md#%E9%87%8D%E6%96%B0%E6%8A%9B%E5%87%BA)重新拋出
正如我們已經注意到的那樣,`.catch`表現得像`try..catch`。我們可以隨心所欲擁有任意多個`.then`,然后使用一個`.catch`在最后來處理它們中的所有錯誤。
在常規`try..catch`中我們可以分析錯誤,如果無法處理,可能會重新拋出錯誤。promise 也是一樣的。如果我們在`.catch`里面`throw`,那么控制流程將轉到下一個最接近的錯誤處理程序。如果我們處理錯誤并正常結束,那么它將繼續執行最接近的`.then`成功處理程序。
在下面的示例中,`.catch`成功處理了錯誤:
~~~js
// 執行流程:catch -> then
new Promise(function(resolve, reject) {
throw new Error("Whoops!");
}).catch(function(error) {
alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
~~~
在這里,`.catch`塊正常結束。然后調用下一個成功處理程序。或者它可以返回一些東西,這和之前的流程相同。
……在這里,`.catch`塊分析錯誤并再次拋出:
~~~js
// 執行順序:catch -> catch -> then
new Promise(function(resolve, reject) {
throw new Error("Whoops!");
}).catch(function(error) { // (*)
if (error instanceof URIError) {
// 處理它
} else {
alert("Can't handle such error");
*!*
throw error; // 拋出這個或別的錯誤,代碼跳轉到下一個 catch
*/!*
}
}).then(function() {
/* 這里永遠不會執行 */
}).catch(error => { // (**)
alert(`The unknown error has occurred: ${error}`);
// 什么都不返回 => 執行正常流程
});
~~~
處理程序`(*)`捕獲錯誤但無法處理它,因為它不是`URIError`,因此它會再次拋出錯誤。然后執行流程跳轉到后面鏈中`(**)`處的下一個`.catch`。
在下面的部分中,我們將看到一個重新拋出的實際例子。
## [](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/article.md#fetch-%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86%E7%A4%BA%E4%BE%8B)Fetch 錯誤處理示例
讓我們改進用戶加載示例的錯誤處理。
在 fetch 不可能發出請求時會返回 rejected 狀態的 promise。例如,遠程服務器不可用或者 URL 格式不對。但是如果遠程服務器響應 404 錯誤或 500 錯誤,那么它會被認為是一個有效的響應。
如果服務器在`(*)`行返回帶有錯誤 500 的非 JSON 頁面怎么辦?如果沒有這樣的用戶并且在`(**)`行 github 返回錯誤 404 的頁面怎么辦?
~~~js
fetch('no-such-user.json') // (*)
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`)) // (**)
.then(response => response.json())
.catch(alert); // SyntaxError: Unexpected token < in JSON at position 0
// ...
~~~
到目前為止,代碼試圖加載響應為 json,無論如何都會因語法錯誤而終止。您可以通過運行上面的示例來看,這是因為該文件`no-such-user.json`并不存在。
這樣并不好,因為錯誤只是說出現在鏈上,沒有細節:失敗的原因和位置。
所以讓我們再添加一步:我們應該檢查`response.status`具有 HTTP 狀態的屬性,如果它不是 200,則拋出錯誤。
~~~js
class HttpError extends Error { // (1)
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) { // (2)
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
loadJson('no-such-user.json') // (3)
.catch(alert); // HttpError: 404 for .../no-such-user.json
~~~
1. 我們為 HTTP 錯誤創建了一個自定義類,以將它們與其他類型的錯誤區分開來。此外,這個新類有一個構造函數,它接受該`response`對象并將其保存在錯誤對象中。因此錯誤處理代碼將能夠訪問它。
2. 然后我們把請求和錯誤處理代碼放到一個方法,該方法會 fetch`url`,**并且**把任何非 200 狀態的響應當成一種錯誤。這很方便,因為我們經常需要這樣的邏輯。
3. 現在`alert`會顯示棒的消息。
擁有我們自己的錯誤類的好處是我們可以在錯誤處理代碼中輕松檢查它。
例如,我們可以發出請求,然后如果我們得到 404 —— 要求用戶修改信息。
下面的代碼從 github 加載具有給定名稱的用戶。如果沒有這樣的用戶,那么它會要求正確的名字:
~~~js
function demoGithubUser() {
let name = prompt("Enter a name?", "iliakan");
return loadJson(`https://api.github.com/users/${name}`)
.then(user => {
alert(`Full name: ${user.name}.`); // (1)
return user;
})
.catch(err => {
*!*
if (err instanceof HttpError && err.response.status == 404) { // (2)
*/!*
alert("No such user, please reenter.");
return demoGithubUser();
} else {
throw err;
}
});
}
demoGithubUser();
~~~
這里:
1. 如果`loadJson`返回一個有效的用戶對象,則在`(1)`行顯示名字,并返回用戶,以便我們可以向鏈中添加更多與用戶相關的操作。在這種情況下`.catch`下面的代碼會被忽略,一切都非常簡單和美好。
2. 否則,如果出現錯誤,我們會在`(2)`行中進行檢查。只有當它確實是 HTTP 錯誤,并且狀態為 404(Not found)時,我們才會要求用戶重新輸入。對于其他錯誤 —— 我們不知道如何處理,所以我們只是重新拋出它們。
## [](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/article.md#%E6%9C%AA%E5%A4%84%E7%90%86%E7%9A%84-rejections)未處理的 rejections
不處理錯誤會發生什么?例如,在上面的例子中重新拋出之后。或者,如果我們忘記將錯誤處理程序附加到鏈的末尾,就像這里:
~~~js
new Promise(function() {
noSuchFunction(); // Error 出現 (沒有此方法)
}); // 沒有加 .catch
~~~
Or here:
~~~js
// 一個沒有 .catch 的 promise 鏈
new Promise(function() {
throw new Error("Whoops!");
}).then(function() {
// ...something...
}).then(function() {
// ...something else...
}).then(function() {
// ...后面沒有 catch!
});
~~~
如果出現錯誤,則 promise 狀態變為 “rejected”,執行應跳轉到最近的 rejection 處理程序。但是上面的例子中沒有這樣的處理程序。所以錯誤會“卡住”。
實際上,這通常是因為不好的代碼導致的。確實,為什么沒有錯誤處理呢?
在這種情況下,大多數 JavaScript 引擎會跟蹤此類情況并生成全局錯誤。我們可以在控制臺中看到它。
在瀏覽器中,我們可以使用`unhandledrejection`事件捕獲它:
~~~js
*!*
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 標準](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections)的一部分。現在,如果發生錯誤,并且沒有`.catch`,則`unhandledrejection`處理程序觸發:`event`對象具有有關錯誤的信息,因此我們可以對其執行某些操作。
通常這樣的錯誤是不可恢復的,因此我們最好的方法是告知用戶有關問題,并且可能的話向服務器報告此事件。
在 Node.js 等非瀏覽器環境中,還有其他類似的方法可以跟蹤未處理的錯誤。
## [](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/article.md#%E6%80%BB%E7%BB%93)總結
總而言之,`.then/catch(handler)`返回一個新的 promise,它根據處理程序的作用而改變:
1. 如果它返回一個值或在沒有`return`(同`return undefined`)的情況下結束,則新的 promise 將變為 resolved,并且用該值作參數調用最近的 resolve 處理程序(`.then`的第一個參數)。
2. 如果它拋出錯誤,則新的 promise 將 rejected,并且用該錯誤作參數調用最接近的 rejection 處理程序(`.then`或`.catch`的第二個參數)。
3. 如果它返回一個 promise,那么 JavaScript 會在它結束前等待,然后以相同的方式對其結果起作用。
圖中展示`.then/catch`變化導致返回 promise 變化:
[](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/promise-handler-variants.png)
小圖中顯示如何調用處理程序:
[](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/03-promise-chaining/promise-handler-variants-2.png)
在上面的錯誤處理示例中,`.catch`始終是鏈中的最后一個。但在實踐中,并非每個 promise 鏈都有`.catch`。就像常規代碼并不總是包在`try..catch`中一樣。
我們應該準確地放置`.catch`在我們想要處理錯誤的地方,并知道如何處理它們。使用自定義錯誤類可以幫助分析錯誤并重新拋出那些我們無法處理的錯誤。
對于超出我們的范圍的錯誤,我們應該用`unhandledrejection`事件處理程序(對于瀏覽器,其它環境同理)。這些未知錯誤通常是不可恢復的,因此我們所要做的就是通知用戶,可能的話向我們的服務器報告此事件。
- 內容介紹
- EcmaScript基礎
- 快速入門
- 常量與變量
- 字符串
- 函數的基本概念
- 條件判斷
- 數組
- 循環
- while循環
- for循環
- 函數基礎
- 對象
- 對象的方法
- 函數
- 變量作用域
- 箭頭函數
- 閉包
- 高階函數
- map/reduce
- filter
- sort
- Promise
- 基本對象
- Arguments 對象
- 剩余參數
- Map和Set
- Json基礎
- RegExp
- Date
- async
- callback
- promise基礎
- promise-api
- promise鏈
- async-await
- 項目實踐
- 標簽系統
- 遠程API請求
- 面向對象編程
- 創建對象
- 原型繼承
- 項目實踐
- Classes
- 構造函數
- extends
- static
- 項目實踐
- 模塊
- import
- export
- 項目實踐
- 第三方擴展庫
- immutable
- Vue快速入門
- 理解MVVM
- Vue中的MVVM模型
- Webpack+Vue快速入門
- 模板語法
- 計算屬性和偵聽器
- Class 與 Style 綁定
- 條件渲染
- 列表渲染
- 事件處理
- 表單輸入綁定
- 組件基礎
- 組件注冊
- Prop
- 自定義事件
- 插槽
- 混入
- 過濾器
- 項目實踐
- 標簽編輯
- iView
- iView快速入門
- 課程講座
- 環境配置
- 第3周 Javascript快速入門