Promise 鏈式調用順序引起的思考

image

前言

近一個多月沒有寫博客了,前陣子一個朋友問我一個關於 Promise 鏈式調用執行順序的問題javascript

image

憑藉我對 Promise 源碼的瞭解,這種問題能難住我?java

image

而後我理所固然的回答錯了git

image

以後再次翻閱了一遍曾經手寫的 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異步

這裏先拋出結論,而後再對題目進行解析函數

結論1

當執行 then 方法時,若是前面的 promise 已是 resolved 狀態,則直接將回調放入微任務隊列中ui

執行 then 方法是同步的,而 then 中的回調是異步的spa

new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log("log: 外部第一個then");
});
複製代碼

實例化 Promise 傳入的函數是同步執行的,then 方法自己其實也是同步執行的,但 then 中的回調會先放入微任務隊列,等同步任務執行完畢後,再依次取出執行,換句話說只有回調是異步的

同時在同步執行 then 方法時,會進行判斷:

  • 若是前面的 promise 已是 resolved 狀態,則會當即將回調推入微任務隊列(可是執行回調仍是要等到全部同步任務都結束後)
  • 若是前面的 promise 是 pending 狀態則會將回調存儲在 promise 的內部,一直等到 promise 被 resolve 纔將回調推入微任務隊列

結論2

當一個 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 個回調推入微任務隊列,此時若是沒有同步任務就會逐個取出再執行

另外還有幾點須要注意:

  1. 對於普通的 promise 來講,當執行完 resolve 函數時,promise 狀態就爲 resolved

resolve 函數就是在實例化 Promise 時,傳入函數的第一個參數

new Promise(resolve => {
  resolve();
});
複製代碼

它的做用除了將當前的 promise 由 pending 變爲 resolved,還會遍歷以前經過 then 給這個 promise 註冊的全部回調,將它們依次放入微任務隊列中,不少人覺得是由 then 方法來觸發它保存回調,而事實上 then 方法即不會觸發回調,也不會將它放到微任務,then 只負責註冊回調,由 resolve 將註冊的回調放入微任務隊列,由事件循環將其取出並執行

具體的行爲能夠參考底部連接

  1. 對於 then 方法返回的 promise 它是沒有 resolve 函數的,取而代之只要 then 中回調的代碼執行完畢並得到同步返回值,這個 then 返回的 promise 就算被 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 註冊的全部回調(沒有),至此所有結束

參考資料

某人寫的 promise

相關文章
相關標籤/搜索