前言
近一個多月沒有寫博客了,前陣子一個朋友問我一個關于 Promise 鏈式調(diào)用執(zhí)行順序的問題
憑借我對 Promise 源碼的了解,這種問題能難住我?
然后我理所當然的回答錯了
之后再次翻閱了一遍曾經(jīng)手寫的 Promise,理清了其中的緣由,寫下這篇文章,希望對 Promise 有更深一層的理解
問題
題目是這樣的,為了更加語義化我將打印的字符串做了一些修改
new Promise((resolve, reject) => {
console.log("log: 外部promise");
resolve();
})
.then(() => {
console.log("log: 外部第一個then");
new Promise((resolve, reject) => {
console.log("log: 內(nèi)部promise");
resolve();
})
.then(() => {
console.log("log: 內(nèi)部第一個then");
})
.then(() => {
console.log("log: 內(nèi)部第二個then");
});
})
.then(() => {
console.log("log: 外部第二個then");
});
// log: 外部promise
// log: 外部第一個then
// log: 內(nèi)部promise
// log: 內(nèi)部第一個then
// log: 外部第二個then
// log: 內(nèi)部第二個then
它的考點并不僅限于 Promise 本身,同時還考察 Promise 鏈式調(diào)用之間的執(zhí)行順序,在開始解析之前,首先要清楚 Promise 能夠鏈式調(diào)用的原理,即
promise 的 then/catch 方法執(zhí)行后會也返回一個 promise
這里先拋出結(jié)論,然后再對題目進行解析
結(jié)論1
當執(zhí)行 then 方法時,如果前面的 promise 已經(jīng)是 resolved 狀態(tài),則直接將回調(diào)放入微任務隊列中
執(zhí)行 then 方法是同步的,而 then 中的回調(diào)是異步的
new Promise((resolve, reject) => {
resolve();
}).then(() => {
console.log("log: 外部第一個then");
});
實例化 Promise 傳入的函數(shù)是同步執(zhí)行的,then 方法也是同步執(zhí)行的,但 then 中的回調(diào)會先放入微任務隊列,等同步任務執(zhí)行完畢后,再依次取出執(zhí)行,換句話說只有回調(diào)是異步的
同時在同步執(zhí)行 then 方法時,會進行判斷:
- 如果前面的 promise 已經(jīng)是 resolved 狀態(tài),則會立即將回調(diào)推入微任務隊列(但是執(zhí)行回調(diào)還是要等到所有同步任務都結(jié)束后)
- 如果前面的 promise 是 pending 狀態(tài)則會將回調(diào)存儲在 promise 的內(nèi)部,一直等到 promise 被 resolve 才將回調(diào)推入微任務隊列
結(jié)論2
當一個 promise 被 resolve 時,會遍歷之前通過 then 給這個 promise 注冊的所有回調(diào),將它們依次放入微任務隊列中
如何理解通過 then 給這個 promise 注冊的所有回調(diào),考慮以下案例
let p = new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
p.then(() => {
console.log("log: 外部第一個then");
});
p.then(() => {
console.log("log: 外部第二個then");
});
p.then(() => {
console.log("log: 外部第三個then");
});
1 秒后變量 p 才會被 resolve,但是在 resolve 前通過 then 方法給它注冊了 3 個回調(diào),此時這 3 個回調(diào)不會被執(zhí)行,也不會被放入微任務隊列中,它們會被 p 內(nèi)部儲存起來,等到 p 被 resolve 后,依次將這 3 個回調(diào)推入微任務隊列,此時如果沒有同步任務就會逐個取出再執(zhí)行
另外還有幾點需要注意:
- 對于普通的 promise 來說,當執(zhí)行完 resolve 函數(shù)時,promise 狀態(tài)就為 resolved
而 resolve 函數(shù)就是在實例化 Promise 時,傳入函數(shù)的第一個參數(shù)
new Promise(resolve => {
resolve();
});
它的作用除了將當前的 promise 由 pending 變?yōu)?resolved,還會遍歷之前通過 then 給這個 promise 注冊的所有回調(diào),將它們依次放入微任務隊列中,很多人以為是由 then 方法來觸發(fā)它保存回調(diào),而事實上是由 promise 的 resolve 來觸發(fā)的,then 方法只負責注冊回調(diào)
具體的行為可以參考底部鏈接
- 對于 then 方法返回的 promise 它是沒有 resolve 函數(shù)的,取而代之只要 then 中回調(diào)的代碼執(zhí)行完畢并獲得同步返回值,這個 then 返回的 promise 就算被 resolve
同步返回值的意思換句話說,如果 then 中的回調(diào)返回了一個 promise,那么 then 返回的 promise 會等待這個 promise 被 resolve 后再 resolve(這句話有點像繞口令哈哈哈~)
new Promise((resolve, reject) => {
resolve();
})
.then(() =>
new Promise((resolve, reject) => {
resolve();
}).then(() => {
console.log("log: 內(nèi)部第一個then");
})
)
.then(() => {
console.log("log: 外部第二個then");
});
// log: 內(nèi)部第一個then
// log: 外部第二個then
這里外部的第一個 then 的回調(diào)返回了一個 promise,所以外部第一個 then 返回的 promise 需要等到內(nèi)部整個 promise (紅框) 被 resolve 后才會被 resolve
當打印 log: 內(nèi)部第一個then 后,回調(diào)執(zhí)行完畢,藍框的 promise 被 resolve,然后外部第一個 then 返回的 promise 才被 resolve
隨后遍歷之前通過 then 給外部第一個 then 返回的 promise 注冊的所有回調(diào)(黃框),放入微任務隊列,等同步任務執(zhí)行完畢后,依次取出執(zhí)行,最終打印 log: 外部第二個then
解析
分析完 promise 和 then 的行為后,我們結(jié)合代碼來解析問題(建議分屏,比對問題章節(jié)中的案例代碼查看解析)
首先 Promise 實例化時,同步執(zhí)行函數(shù),打印 log: 外部promise,然后執(zhí)行 resolve 函數(shù),將 promise 變?yōu)?resolved,但由于此時 then 方法還未執(zhí)行,所以遍歷所有 then 方法注冊的回調(diào)時什么也不會發(fā)生(結(jié)論2第一條)
此時剩余任務如下:
主線程:外部第一個 then,外部第二個 then
微任務隊列:空
接著執(zhí)行外部第一個 then(以下簡稱:外1then),由于前面的 promise 已經(jīng)被 resolve,所以立即將回調(diào)放入微任務隊列(結(jié)論1)
主線程:外2then
微任務隊列:外1then 的回調(diào)
但是由于此時這個回調(diào)還未執(zhí)行,所以外1then 返回的 promise 仍為 pending 狀態(tài)(結(jié)論2第二條),繼續(xù)同步執(zhí)行外2then,由于前面的 promise 是 pending 狀態(tài),所以外2then 的回調(diào)也不會被推入微任務隊列也不會執(zhí)行(結(jié)論2案例)
主線程:空
微任務隊列:外1then 的回調(diào)
當主線程執(zhí)行完畢后,執(zhí)行微任務,也就是外1then 的回調(diào),回調(diào)中首先打印log: 外部第一個then
隨后實例化內(nèi)部 promise,在實例化時執(zhí)行函數(shù),打印 log: 內(nèi)部promise,然后執(zhí)行 resolve 函數(shù)(結(jié)論1),接著執(zhí)行到內(nèi)部的第一個 then(內(nèi)1then),由于前面的 promise 已被 resolve,所以將回調(diào)放入微任務隊列中(結(jié)論1)
主線程:內(nèi)2then
微任務隊列:內(nèi)1then 的回調(diào)
由于正在執(zhí)行外1then 的回調(diào),所以外1then 返回的 promise 仍是 pending 狀態(tài),外2then 的回調(diào)仍不會被注冊也不會被執(zhí)行
接著同步執(zhí)行內(nèi)2then,由于它前面的 promise (內(nèi)1then 返回的 promise) 是 pending 狀態(tài)(因為內(nèi)1then 的回調(diào)在微任務隊列中,還未執(zhí)行),所以內(nèi)2then 的回調(diào)和外2then 的回調(diào)一樣,不注冊不執(zhí)行(結(jié)論2案例)
主線程:空
微任務隊列:內(nèi)1then 的回調(diào)
此時外1then 的回調(diào)全部執(zhí)行完畢,外1then 返回的 promise 的狀態(tài)由 pending 變?yōu)?resolved(結(jié)論2第二條),同時遍歷之前通過 then 給這個 promise 注冊的所有回調(diào),將它們的回調(diào)放入微任務隊列中(結(jié)論2),也就是外2then 的回調(diào)
主線程:空
微任務隊列:內(nèi)1then 的回調(diào),外2then 的回調(diào)
此時主線程邏輯執(zhí)行完畢,取出第一個微任務執(zhí)行
主線程:內(nèi)1then 的回調(diào)
微任務隊列:外2then 的回調(diào)
執(zhí)行內(nèi)1then 的回調(diào)打印 log: 內(nèi)部第一個then,回調(diào)執(zhí)行完畢后,內(nèi)1then 返回的 promise 由 pending 變?yōu)?resolved(結(jié)論2第二條),同時遍歷之前通過 then 給這個 promise 注冊的所有回調(diào),將它們的回調(diào)放入微任務隊列中(結(jié)論2),也就是內(nèi)2then 的回調(diào)
主線程:空
微任務隊列:外2then 的回調(diào),內(nèi)2then 的回調(diào)
執(zhí)行外2then 的回調(diào)打印 log: 外部第二個then,回調(diào)執(zhí)行完畢,外2then 返回的 promise 由 pending 變?yōu)?resolved(結(jié)論2第二條),同時遍歷之前通過 then 給這個 promise 注冊的所有回調(diào),將它們放入微任務隊列中(結(jié)論2)
這時由于外2 then 返回的 promise 沒有再進一步的鏈式調(diào)用了,主線程任務結(jié)束
主線程:空
微任務隊列:內(nèi)2then 的回調(diào)
接著取出微任務,執(zhí)行內(nèi)2then 的回調(diào)打印 log: 內(nèi)部第二個then,內(nèi)2then 返回的 promise 的狀態(tài)變?yōu)?resolved(結(jié)論2第二條),同時遍歷之前通過 then 給這個 promise 注冊的所有回調(diào)(沒有),至此全部結(jié)束