近一個多月沒有寫博客了,前陣子一個朋友問我一個關於 Promise 鏈式調用執行順序的問題javascript
憑藉我對 Promise 源碼的瞭解,這種問題能難住我?java
而後我理所固然的回答錯了git
以後再次翻閱了一遍曾經手寫的 Promise,理清了其中的原因,寫下這篇文章,但願對 Promise 有更深一層的理解github
題目是這樣的,爲了更加語義化我將打印的字符串作了一些修改數組
new Promise((resolve, reject) => {
console.log("log: 外部promise");
resolve();
})
.then(() => {
console.log("log: 外部第一個then");
new Promise((resolve, reject) => {
console.log("log: 內部promise");
resolve();
})
.then(() => {
console.log("log: 內部第一個then");
})
.then(() => {
console.log("log: 內部第二個then");
});
})
.then(() => {
console.log("log: 外部第二個then");
});
// log: 外部promise
// log: 外部第一個then
// log: 內部promise
// log: 內部第一個then
// log: 外部第二個then
// log: 內部第二個then
複製代碼
它的考點並不只限於 Promise 自己,同時還考察 Promise 鏈式調用之間的執行順序,在開始解析以前,首先要清楚 Promise 可以鏈式調用的原理,即promise
promise 的 then/catch 方法執行後會也返回一個 promise
異步
這裏先拋出結論,而後再對題目進行解析函數
當執行 then 方法時,若是前面的 promise 已是 resolved 狀態,則直接將回調放入微任務隊列中ui
執行 then 方法是同步的,而 then 中的回調是異步的spa
new Promise((resolve, reject) => {
resolve();
}).then(() => {
console.log("log: 外部第一個then");
});
複製代碼
實例化 Promise 傳入的函數是同步執行的,then 方法自己其實也是同步執行的,但 then 中的回調會先放入微任務隊列,等同步任務執行完畢後,再依次取出執行,換句話說只有回調是異步的
同時在同步執行 then 方法時,會進行判斷:
當一個 promise 被 resolve 時,會遍歷以前經過 then 給這個 promise 註冊的全部回調,將它們依次放入微任務隊列中
如何理解經過 then 給這個 promise 註冊的全部回調
,考慮如下案例
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 個回調,此時這 3 個回調不會被執行,也不會被放入微任務隊列中,它們會被 p 內部儲存起來(在手寫 promise 時,這些回調會放在 promise 內部保存的數組中),等到 p 被 resolve 後,依次將這 3 個回調推入微任務隊列,此時若是沒有同步任務就會逐個取出再執行
另外還有幾點須要注意:
resolve 函數就是在實例化 Promise 時,傳入函數的第一個參數
new Promise(resolve => {
resolve();
});
複製代碼
它的做用除了將當前的 promise 由 pending 變爲 resolved,還會遍歷以前經過 then 給這個 promise 註冊的全部回調,將它們依次放入微任務隊列中,不少人覺得是由 then 方法來觸發它保存回調,而事實上 then 方法即不會觸發回調,也不會將它放到微任務,then 只負責註冊回調,由 resolve 將註冊的回調放入微任務隊列,由事件循環將其取出並執行
具體的行爲能夠參考底部連接
同步返回值
的意思換句話說,若是 then 中的回調返回了一個 promise,那麼 then 返回的 promise 會等待這個 promise 被 resolve 後再 resolve(這句話有點像繞口令哈哈哈~)
new Promise((resolve, reject) => {
resolve();
})
.then(() =>
new Promise((resolve, reject) => {
resolve();
}).then(() => {
console.log("log: 內部第一個then");
})
)
.then(() => {
console.log("log: 外部第二個then");
});
// log: 內部第一個then
// log: 外部第二個then
複製代碼
這裏外部的第一個 then 的回調返回了一個 promise (紅框),因此外部第一個 then 返回的 promise (綠框)須要等到內部整個 promise (紅框) 被 resolve 後纔會被 resolve(實質上只要藍框的 promise 被 resolve,紅框的 promise 就算被 resolve 了)
當打印 log: 內部第一個then
後,回調執行完畢,藍框的 promise 先被 resolve(即紅框的 promise 被 resolve),而後綠框的 promise 纔算被 resolve
一旦綠框被 resolve,就會遍歷以前經過 then 給綠框的 promise 註冊的全部回調(黃框),放入微任務隊列,等同步任務執行完畢後,依次取出執行,最終打印 log: 外部第二個then
分析完 promise 和 then 的行爲後,咱們結合代碼來解析問題(建議分屏,比對問題章節中的案例代碼查看解析)
首先 Promise 實例化時,同步執行函數,打印 log: 外部promise
,而後執行 resolve 函數,將 promise 變爲 resolved,但因爲此時 then 方法還未執行,因此遍歷全部 then 方法註冊的回調時什麼也不會發生(結論2第一條)
此時剩餘任務以下:
主線程:外部第一個 then,外部第二個 then
微任務隊列:空
接着執行外部第一個 then(如下簡稱:外1then),因爲前面的 promise 已經被 resolve,因此當即將回調放入微任務隊列(結論1)
主線程:外2then
微任務隊列:外1then 的回調
可是因爲此時這個回調還未執行,因此外1then 返回的 promise 仍爲 pending 狀態(結論2第二條),繼續同步執行外2then,因爲前面的 promise 是 pending 狀態,因此外2then 的回調也不會被推入微任務隊列也不會執行(結論2案例)
主線程:空
微任務隊列:外1then 的回調
當主線程執行完畢後,執行微任務,也就是外1then 的回調,回調中首先打印log: 外部第一個then
隨後實例化內部 promise,在實例化時執行函數,打印 log: 內部promise
,而後執行 resolve 函數(結論1),接着執行到內部的第一個 then(如下簡稱:內1then),因爲前面的 promise 已被 resolve,因此將回調放入微任務隊列中(結論1)
主線程:內2then
微任務隊列:內1then 的回調
因爲正在執行外1then 的回調,因此外1then 返回的 promise 還是 pending 狀態,外2then 的回調仍不會被註冊也不會被執行
接着同步執行內2then,因爲它前面的 promise (內1then 返回的 promise) 是 pending 狀態(由於內1then 的回調在微任務隊列中,還未執行),因此內2then 的回調和外2then 的回調同樣,不註冊不執行(結論2案例)
主線程:空
微任務隊列:內1then 的回調
此時外1then 的回調所有執行完畢,外1then 返回的 promise 的狀態由 pending 變爲 resolved(結論2第二條),同時遍歷以前經過 then 給這個 promise 註冊的全部回調,將它們的回調放入微任務隊列中(結論2),即放入外2then 的回調
主線程:空
微任務隊列:內1then 的回調,外2then 的回調
此時主線程邏輯執行完畢,取出第一個微任務執行
主線程:內1then 的回調
微任務隊列:外2then 的回調
執行內1then 的回調打印 log: 內部第一個then
,回調執行完畢後,內1then 返回的 promise 由 pending 變爲 resolved(結論2第二條),同時遍歷以前經過 then 給這個 promise 註冊的全部回調,將它們的回調放入微任務隊列中(結論2),即放入內2then 的回調
主線程:空
微任務隊列:外2then 的回調,內2then 的回調
執行外2then 的回調打印 log: 外部第二個then
,回調執行完畢,外2then 返回的 promise 由 pending 變爲 resolved(結論2第二條),同時遍歷以前經過 then 給這個 promise 註冊的全部回調,將它們放入微任務隊列中(結論2)
這時因爲外2 then 返回的 promise 沒有再進一步的鏈式調用了,主線程任務結束
主線程:空
微任務隊列:內2then 的回調
接着取出微任務,執行內2then 的回調打印 log: 內部第二個then
,內2then 返回的 promise 的狀態變爲 resolved(結論2第二條),同時遍歷以前經過 then 給這個 promise 註冊的全部回調(沒有),至此所有結束