前言
解讀了同步執行 resolve 的程式碼,接下來要看的則是非同步執行 resolve了。非同步總會比同步複雜得多,它不會按照順序執行,所以程式碼會跳來跳去地閱讀。
與同步不同的是,非同步時程式碼有可能會先執行 then 函式,將 then 的回撥函式儲存起來,等到執行 resolve 的時候,再將其取出執行。
new Promise(function (resolve) {
setTimeout(function () {
resolve(1);
}, 1000);
}.then(function (val) {
console.log(val);
});
複製程式碼
注:本次閱讀的是 then/promise 的 3.0.0 版本,原始碼請戳 這裡。
解讀
這一次的解讀會按照以下的一個執行順序來進行:
constructor -> fn --非同步--> then -> resolve(reject) -> then 回撥
複製程式碼
then
從 fn 函式執行後開始,這時來到了 then
函式:
this.then = function(onFulfilled, onRejected) {
return new Promise(function(resolve, reject) {
handle({ onFulfilled: onFulfilled, onRejected: onRejected, resolve: resolve, reject: reject })
})
}
複製程式碼
同樣先不用管返回的 Promise 例項和它的引數,我們只需要知道 onFulfilled 和 onRejected 被作為引數傳遞給了 handle
函式。可以把以上程式碼簡化成以下:
this.then = function(onFulfilled, onRejected) {
handle({ onFulfilled: onFulfilled, onRejected: onRejected })
}
複製程式碼
handle
來看看 handle
函式是怎麼處理 onFulfilled 函式(即 then 的回撥函式)的。簡化了許多程式碼:
function handle(deferred) {
if (state === null) {
deferreds.push(deferred)
return
}
}
複製程式碼
state
是建構函式裡定義的一個變數,它主要作用是用來記錄狀態,執行 resolve 成功賦值 true,執行 reject 成功賦值 false,初始為 null。
所以此時還沒呼叫 resolve(reject),state 就為 null。這時就用另一個變數 deferreds
來儲存 handle 傳遞進來的引數,即 then 的回撥函式。
deferreds 是一個陣列,由於我們可以多次呼叫 then 函式,所以需要一個陣列來儲存那些回撥函式。類似於這樣的:
var p = new Promise(function (resolve) {
setTimeout(function () {
resolve(1);
}, 1000);
}
p.then(function (val) {
console.log(val);
});
p.then(function (val) {
console.log(val + 1);
});
複製程式碼
至此,then 的回撥函式已經被儲存起來了,就等著非同步執行完畢後 resolve
被呼叫了。
resolve
假設非同步執行完畢了,開始呼叫 resolve
函式。
function resolve(newValue) {
resolve_(newValue)
}
function resolve_(newValue) {
if (state !== null)
return
try {
state = true
value = newValue
finale()
} catch (e) { reject_(e) }
}
複製程式碼
同樣簡化了一下程式碼,去掉暫時不用到的。state
儲存 resolve 後的狀態,value
儲存 resolve 的引數。然後呼叫 finale
函式。
finale
來到 finale
函式,它將取出之前 deferreds
陣列儲存的 then 回撥函式,再傳給 handle
函式,讓 handle 函式來執行。
function finale() {
for (var i = 0, len = deferreds.length; i < len; i++)
handle(deferreds[i])
deferreds = null
}
複製程式碼
handle
好了,又回到了 handle
函式,這一次程式碼跟剛剛的不一樣了。以下程式碼展示忽略 deferred.resolve 和 deferred.reject 的呼叫,我們的關注點在於 onFulfilled
:
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)
})
}
複製程式碼
嗯,是的,這一步跟同步執行 resolve 的最後一步是一樣的。說實話這裡的 nextTick 我也還沒搞懂為啥要用,感覺去掉也不影響使用。
不過不影響整體的閱讀,現在知道這次呼叫 handle 是為了呼叫 onFulfilled
函式,即 then 的回撥函式被執行了。
handle 函式的完整程式碼是這樣的:
function handle(deferred) {
if (state === null) {
deferreds.push(deferred)
return
}
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)
})
}
複製程式碼
程式碼執行了兩次 handle 函式,它有兩個用處。在 state 為 null 時,它用來儲存回撥函式 onFulfilled。再次呼叫它時,resolve 已被執行,state 被修改成 true,則用來執行 onFulfilled。
總結
Promise 非同步執行程式碼時會比較的繞:
new Promise(function (resolve) {
setTimeout(function () {
resolve(1);
}, 1000);
}.then(function (val) {
console.log(val);
});
複製程式碼
執行的順序回顧一下:
constructor -> fn --非同步--> then -> resolve(reject) -> then 回撥
複製程式碼
執行程式碼時,會先執行建構函式,然後是傳入建構函式的 fn 函式。緊接著是執行 then 函式,將其回撥函式 onFulfilled 儲存在 deferreds 陣列中。
等到 fn 函式裡的非同步程式碼執行完畢後,呼叫 resolve 函式,將儲存在 deferreds 陣列裡的回撥函式取出執行。