前言
接下來要深入的是 then 鏈式呼叫,這個是實現中最繞的一塊。在解讀之前,我們再加深一下印象。
constructor -> fn --同步--> resolve(reject) -> then -> then 回撥
constructor -> fn --非同步--> then -> resolve(reject) -> then 回撥
複製程式碼
無論是同步還是非同步的情況,then 回撥函式
都會在 resolve
執行之後,才會執行。所以可以這樣理解,只有執行了 resolve
之後,才會觸發 then 回撥函式
的執行。
注:本次閱讀的是 then/promise 的 3.0.0 版本,原始碼請戳 這裡。
解讀
先來看下怎麼示例程式碼:
var p1 = new Promise(function (resolve) {
setTimeout(function () {
resolve(1);
}, 1000);
})
p1.then(function (val) {
var p3 = new Promise(function (resolve) {
setTimeout(function () {
resolve(val + 1);
}, 1000);
});
return p3;
}).then(function (val) {
console.log(val);
});
複製程式碼
看到上面的程式碼,我們能顯示地看到 new 了兩個 Promise 例項,分別用 p1
和 p3
表示。然後再從之前 then 方法的實現中,我們會看到它也 return 了一個 Promise 例項,我們用 p2
來表示 then 函式執行後返回的 Promise 例項,以便接下來的區分。
this.then = function(onFulfilled, onRejected) {
return new Promise(function(resolve, reject) {
handle({ onFulfilled: onFulfilled, onRejected: onRejected, resolve: resolve, reject: reject })
})
}
複製程式碼
對著上面的程式碼,我們再看下變數分別表示著啥:
- p1:最外層的 Promise 例項;
- p2:p1 呼叫 then 函式返回的 Promise 例項;
- p3:p1 的 then 回撥函式返回的 Promise 例項。
執行順序
理一下上面示例程式碼的執行順序:
- new 了一個 Promise 例項賦值給 p1,並執行 fn 函式;
- p1 呼叫它的 then 函式,返回 p2;
- p2 呼叫它的 then 函式(實際上又返回一個新的 Promise 例項);
- 一秒後,執行 p1 的 resolve 函式;
- 觸發 p1 的 then 回撥函式,此時 p3 被建立並返回;
- 再一秒後,p3 執行 resolve 函式;
- 觸發 p2 的 then 回撥函式。
疑惑:這裡 p3 執行了它的 resolve 函式,怎麼會觸發 p2 的 then 回撥函式,不應該是觸發自己的 then 回撥函式的嗎??
handle
從第 5 步開始看程式碼,觸發 p1 的 then 回撥函式,也就會觸發 p1 裡的 handle 函式:
function handle(deferred) {
nextTick(function() {
var cb = state ? deferred.onFulfilled : deferred.onRejected
if (typeof cb !== 'function'){
(state ? deferred.resolve : deferred.reject)(value)
return
}
var ret
try {
ret = cb(value)
}
catch (e) {
deferred.reject(e)
return
}
deferred.resolve(ret)
})
}
複製程式碼
首先會執行 onFulfilled
函式,即 p1
的 then 回撥函式,此時建立 p3
返回賦值給 ret。然後再呼叫 deferred 的 resolve,其實是呼叫了 p2
的 resolve,將 p3
作為引數傳入。
resolve
deferred 呼叫 resolve,將不會執行的程式碼先去掉:
function resolve(newValue) {
if (delegating)
return
resolve_(newValue)
}
function resolve_(newValue) {
if (state !== null)
return
try {
if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.')
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then
if (typeof then === 'function') {
delegating = true
then.call(newValue, resolve_, reject_)
return
}
}
} catch (e) { reject_(e) }
}
複製程式碼
因為 newValue 就是 p3
,判斷它是一個帶有 then 的物件後,會呼叫它的 then 函式,將 p2
的 resolve_ 作為 p3
的 then 回撥函式傳入。
如果 p3
執行了 resolve,即會執行它的 then 回撥函式,即 p2
的 resolve_。如果 p2
執行了 resolve_,即會執行自己的 then 回撥函式。
這裡可以把程式碼視為這樣,resolve_ 裡的變數都是 p2
的變數:
then.call(newValue, function resolve_ (newValue) {
// 更新狀態
state = true
// 記住 resolve 的引數
value = newValue
finale()
}, reject_);
複製程式碼
而 p2
的 then 回撥函式,就是示例程式碼裡那個鏈式呼叫的 then 的回撥函式。所以就實現了 then 的鏈式呼叫。
說了上面一堆話,可能有點難理解,來張圖配合理解一下吧。
delegating
簡單講一下變數 delegating
,因為 p2
的 resolve_ 可以通過呼叫自己 resolve 來觸發,也可以通過 p3
的 then 回撥來觸發。當通過 p3
的 then 回撥來觸發時,delegating
將設定為 true,此時就切斷了 p2
通過呼叫自己 resolve 來觸發的情況。
function resolve(newValue) {
if (delegating)
return
resolve_(newValue)
}
複製程式碼
nextTick
在知道了整個 then 鏈式呼叫的順序之後,最後來看看 nextTick
。以我的理解,使用 nextTick
是為了讓 then 回撥函式在所以的 then 程式碼執行後才會執行。
否則,以下程式碼,p1
回撥函式先執行,再執行第二個 then 函式,這就違背了 ES6 中 Promise 是微觀任務佇列的概念了。
var p1 = new Promise(function (resolve) {
resolve(1);
})
p1.then(function (val) {
var p3 = new Promise(function (resolve) {
resolve(val + 1);
});
return p3;
}).then(function (val) {
console.log(val);
});
複製程式碼
總結
Promise 的 then 鏈式呼叫,簡單來說就是用一個新的 Promise 例項來做橋樑,當前面 then 回撥函式裡返回的 Promise 執行 resolve,就會觸發橋樑去執行它的 then 回撥。
如果有點難理解,莫著急,重新多看幾遍文章理解一下。