非同步回撥的泥潭
非同步回撥是最直接的非同步結果處理模式,將一個回撥函式callback
扔進非同步處理函式中,當非同步處理獲得結果之後再呼叫這個回撥函式就可以繼續之後的處理,但是如果這個callback
又是一個非同步呼叫呢?眾所周知的,在JavaScript中非同步回撥的層層巢狀幾乎是反人性的,程式碼編寫、修改和閱讀對我等有程式碼潔癖的人而言是一種煎熬,這便是非同步回撥泥潭了。
今天對於處理非同步呼叫已經有了很多成熟的方案,在我看來這些方案都無外乎在解決一個問題:“如何能看似順序地傳遞非同步呼叫的結果?”,本文要說的Promise
就是ES6原生提供的一個解決方案。
在對
Promise
進行敘述之前,依舊引用阮大的《ECMAScript 6入門》一書中的Promise章節便於大家更嚴謹和全面的學習和參考。
Promise
承諾,即對未來的許諾,如果諾言實現,然後(then
)就如何如何……Promise
極其生動的講述了一個言出必行的故事。
new Promise(function(resolve, reject){
//開始實現承諾
....
....
if(承諾兌現時) {
resolve(dollars); //兌現承諾的結果是得到`一大筆美金`
} else {
reject(`絕交`); //沒兌現承諾就絕交
}
}).then(function(dollars){ //然後有錢了,買房買車娶妻生子
let d1 = buyHouse(dollars); //把每次消費剩餘的錢傳給下一個函式
let d2 = buyCar(d1);
let d3 = marry(d2);
makeBaby(d3);
}).catch(function(result){//然後如果絕交了,還是繼續吃土
//繼續吃土
});
console.log(`故事開始....`);
看過上面的這個俗不可耐的故事之後需要理解幾件事情:
-
言出必行:一個
Promise
構造出來之後,構造時傳入的非同步函式就立即執行;*
注:因大凡使用promise都是在非同步呼叫場景,下文所說的非同步函式都是指構造promise時傳入的函式* -
Promise
例項內部維護了一個狀態機,狀態變化只可能是pending
到resolved
或者pending
到rejected
;-
執行
resolve
:pending
變化到resolved
-
執行
reject
:pending
變化到rejected
-
丟擲錯誤:
pending
變化到rejected
-
-
then
的第一個回撥函式只會在發生了resolve
之後執行,本質上是在Promise
到達resolved
狀態執行; -
then
的第二個回撥函式或者catch
的回撥函式會在發生reject
之後或者非同步函式執行丟擲錯誤時執行,本質上是在promise
到達rejected
狀態時執行; -
非同步函式執行得到結果可以通過
resolve
或者reject
將結果傳出;-
呼叫
resolve
傳入的值會作為then
第一個回撥函式的入參 -
呼叫
reject
傳入的值作為then
第二個回撥函式或者catch
的回撥函式的入參 -
如果非同步函式丟擲了異常,異常會作為
then
第二個回撥函式或者catch
的回撥函式的入參
-
-
`故事開始….`會先輸出,而不是等到
then
的回撥函式執行完畢才輸出,說明傳入then
的回撥函式是非同步執行,同理catch
也是一樣;
非同步函式呼叫鏈
then
和catch
都是Promise
的例項方法,都返回一個新的Promise
,因此可以輕而易舉地實現鏈式程式設計,比如上面的例子中“把每次消費剩餘的錢”傳給下一個函式可以改寫成這樣:
....//前面省略
.then(function(dollars){
return buyHouse(dollars);
}).then(function(d1){
return buyCar(d1);
}).then(function(d2){
return marry(d2);
}).then(function(d3){
return makeBaby(d3);
}).catch(function(result){
//繼續吃土
});
看到這裡你可能認為前一個then
回撥函式的返回值是後一個then
的回撥函式的入參,但這是不準確的,因為當then
回撥函式返回的是個Promise
物件時,這個Promise
物件到終態時後一個then
才會執行,並且該Promise
物件執行resolve
時的入參才是後一個then
的回撥函式入參;
此時有必要對Promise
的一個類方法resolve
做以下說明,它的特性兩句話:
-
如果傳入的是個
Promise
物件,則直接返回這個Promise
; -
如果是其他任何一個值(包括Error物件和undefined)則直接轉換為一個
resolved
狀態的Promise
物件;
比如說下面的程式碼:
//以下的p1和p2邏輯上等同
let p1 = Promise.resolve(1);
let p2 = new Promise(function(resolve, reject) {
resolve(1);
});
//以下的p3和p4等同
let p3 = new Promise(function(r, j) {});
let p4 = Promise.resolve(p3);
console.log(p3 == p4); //true
console.log(p3 === p4); //true
//以下三者邏輯上等同
Promise.resolve().then(function(dollars) {
return 1 + 1;
}).then(function(v) {
console.log(v);
});
Promise.resolve().then(function(dollars) {
return new Promise(function(r, j) { r(1 + 1) });
}).then(function(v) {
console.log(v);
});
Promise.resolve().then(function(dollars) {
return Promise.resolve(1 + 1);
}).then(function(v) {
console.log(v);
});
我們可以利用Promise
非同步執行結果傳出的機制和then
的鏈式呼叫,將層層巢狀的函式呼叫變為通過then
順序連線的鏈式呼叫
從寫法和形式上看是不是人性很多呢?
通過Promise
實現的鏈式非同步函式呼叫,以斐波那契數列舉例如下:
//一個非同步的斐波那契計算
function fibonacci(v) {
return new Promise(function(resolve, reject) { //每一個非同步呼叫都返回了一個Promise
setTimeout(function() {
console.log(`${v.a}`);
[v.a, v.b] = [v.b, v.a + v.b];
resolve(v);
}, 500);
});
}
//以下兩者邏輯等同,每個then都等待上一個promise的結果形成一條鏈。
// fibonacci({ a: 0, b: 1 })
// .then(fibonacci)
// .then(fibonacci)
// .then(fibonacci)
// .then(fibonacci)
// .then(fibonacci)
// .then(fibonacci);
Promise.resolve()
.then(() => fibonacci({ a: 0, b: 1 }))
.then(fibonacci)
.then(fibonacci)
.then(fibonacci)
.then(fibonacci)
.then(fibonacci)
.then(fibonacci);