Promise 鏈?zhǔn)秸{(diào)用順序引發(fā)的思考

image

前言

近一個(gè)多月沒(méi)有寫博客了,前陣子一個(gè)朋友問(wèn)我一個(gè)關(guān)于 Promise 鏈?zhǔn)秸{(diào)用執(zhí)行順序的問(wèn)題

image

憑借我對(duì) Promise 源碼的了解,這種問(wèn)題能難住我?

image

然后我理所當(dāng)然的回答錯(cuò)了

image

之后再次翻閱了一遍曾經(jīng)手寫的 Promise,理清了其中的緣由,寫下這篇文章,希望對(duì) Promise 有更深一層的理解

問(wèn)題

題目是這樣的,為了更加語(yǔ)義化我將打印的字符串做了一些修改

new Promise((resolve, reject) => {
  console.log("log: 外部promise");
  resolve();
})
  .then(() => {
    console.log("log: 外部第一個(gè)then");
    new Promise((resolve, reject) => {
      console.log("log: 內(nèi)部promise");
      resolve();
    })
      .then(() => {
        console.log("log: 內(nèi)部第一個(gè)then");
      })
      .then(() => {
        console.log("log: 內(nèi)部第二個(gè)then");
      });
  })
  .then(() => {
    console.log("log: 外部第二個(gè)then");
  });
  
// log: 外部promise
// log: 外部第一個(gè)then
// log: 內(nèi)部promise
// log: 內(nèi)部第一個(gè)then
// log: 外部第二個(gè)then
// log: 內(nèi)部第二個(gè)then

它的考點(diǎn)并不僅限于 Promise 本身,同時(shí)還考察 Promise 鏈?zhǔn)秸{(diào)用之間的執(zhí)行順序,在開始解析之前,首先要清楚 Promise 能夠鏈?zhǔn)秸{(diào)用的原理,即

promise 的 then/catch 方法執(zhí)行后會(huì)也返回一個(gè) promise

這里先拋出結(jié)論,然后再對(duì)題目進(jìn)行解析

結(jié)論1

當(dāng)執(zhí)行 then 方法時(shí),如果前面的 promise 已經(jīng)是 resolved 狀態(tài),則直接將回調(diào)放入微任務(wù)隊(duì)列中

執(zhí)行 then 方法是同步的,而 then 中的回調(diào)是異步的

new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log("log: 外部第一個(gè)then");
});

實(shí)例化 Promise 傳入的函數(shù)是同步執(zhí)行的,then 方法也是同步執(zhí)行的,但 then 中的回調(diào)會(huì)先放入微任務(wù)隊(duì)列,等同步任務(wù)執(zhí)行完畢后,再依次取出執(zhí)行,換句話說(shuō)只有回調(diào)是異步的

同時(shí)在同步執(zhí)行 then 方法時(shí),會(huì)進(jìn)行判斷:

  • 如果前面的 promise 已經(jīng)是 resolved 狀態(tài),則會(huì)立即將回調(diào)推入微任務(wù)隊(duì)列(但是執(zhí)行回調(diào)還是要等到所有同步任務(wù)都結(jié)束后)
  • 如果前面的 promise 是 pending 狀態(tài)則會(huì)將回調(diào)存儲(chǔ)在 promise 的內(nèi)部,一直等到 promise 被 resolve 才將回調(diào)推入微任務(wù)隊(duì)列

結(jié)論2

當(dāng)一個(gè) promise 被 resolve 時(shí),會(huì)遍歷之前通過(guò) then 給這個(gè) promise 注冊(cè)的所有回調(diào),將它們依次放入微任務(wù)隊(duì)列中

如何理解通過(guò) then 給這個(gè) promise 注冊(cè)的所有回調(diào),考慮以下案例

let p = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000);
});
p.then(() => {
  console.log("log: 外部第一個(gè)then");
});
p.then(() => {
  console.log("log: 外部第二個(gè)then");
});
p.then(() => {
  console.log("log: 外部第三個(gè)then");
});

1 秒后變量 p 才會(huì)被 resolve,但是在 resolve 前通過(guò) then 方法給它注冊(cè)了 3 個(gè)回調(diào),此時(shí)這 3 個(gè)回調(diào)不會(huì)被執(zhí)行,也不會(huì)被放入微任務(wù)隊(duì)列中,它們會(huì)被 p 內(nèi)部?jī)?chǔ)存起來(lái),等到 p 被 resolve 后,依次將這 3 個(gè)回調(diào)推入微任務(wù)隊(duì)列,此時(shí)如果沒(méi)有同步任務(wù)就會(huì)逐個(gè)取出再執(zhí)行

另外還有幾點(diǎn)需要注意:

  1. 對(duì)于普通的 promise 來(lái)說(shuō),當(dāng)執(zhí)行完 resolve 函數(shù)時(shí),promise 狀態(tài)就為 resolved

而 resolve 函數(shù)就是在實(shí)例化 Promise 時(shí),傳入函數(shù)的第一個(gè)參數(shù)

new Promise(resolve => {
  resolve();
});

它的作用除了將當(dāng)前的 promise 由 pending 變?yōu)?resolved,還會(huì)遍歷之前通過(guò) then 給這個(gè) promise 注冊(cè)的所有回調(diào),將它們依次放入微任務(wù)隊(duì)列中,很多人以為是由 then 方法來(lái)觸發(fā)它保存回調(diào),而事實(shí)上是由 promise 的 resolve 來(lái)觸發(fā)的,then 方法只負(fù)責(zé)注冊(cè)回調(diào)

具體的行為可以參考底部鏈接

  1. 對(duì)于 then 方法返回的 promise 它是沒(méi)有 resolve 函數(shù)的,取而代之只要 then 中回調(diào)的代碼執(zhí)行完畢并獲得同步返回值,這個(gè) then 返回的 promise 就算被 resolve

同步返回值的意思換句話說(shuō),如果 then 中的回調(diào)返回了一個(gè) promise,那么 then 返回的 promise 會(huì)等待這個(gè) promise 被 resolve 后再 resolve(這句話有點(diǎn)像繞口令哈哈哈~)

new Promise((resolve, reject) => {
  resolve();
})
  .then(() =>
    new Promise((resolve, reject) => {
      resolve();
    }).then(() => {
      console.log("log: 內(nèi)部第一個(gè)then");
    })
  )
  .then(() => {
    console.log("log: 外部第二個(gè)then");
  });
  
  // log: 內(nèi)部第一個(gè)then
  // log: 外部第二個(gè)then

這里外部的第一個(gè) then 的回調(diào)返回了一個(gè) promise,所以外部第一個(gè) then 返回的 promise 需要等到內(nèi)部整個(gè) promise (紅框) 被 resolve 后才會(huì)被 resolve

image

當(dāng)打印 log: 內(nèi)部第一個(gè)then 后,回調(diào)執(zhí)行完畢,藍(lán)框的 promise 被 resolve,然后外部第一個(gè) then 返回的 promise 才被 resolve

隨后遍歷之前通過(guò) then 給外部第一個(gè) then 返回的 promise 注冊(cè)的所有回調(diào)(黃框),放入微任務(wù)隊(duì)列,等同步任務(wù)執(zhí)行完畢后,依次取出執(zhí)行,最終打印 log: 外部第二個(gè)then

解析

分析完 promise 和 then 的行為后,我們結(jié)合代碼來(lái)解析問(wèn)題(建議分屏,比對(duì)問(wèn)題章節(jié)中的案例代碼查看解析)

首先 Promise 實(shí)例化時(shí),同步執(zhí)行函數(shù),打印 log: 外部promise,然后執(zhí)行 resolve 函數(shù),將 promise 變?yōu)?resolved,但由于此時(shí) then 方法還未執(zhí)行,所以遍歷所有 then 方法注冊(cè)的回調(diào)時(shí)什么也不會(huì)發(fā)生(結(jié)論2第一條)

此時(shí)剩余任務(wù)如下:

主線程:外部第一個(gè) then,外部第二個(gè) then

微任務(wù)隊(duì)列:空

接著執(zhí)行外部第一個(gè) then(以下簡(jiǎn)稱:外1then),由于前面的 promise 已經(jīng)被 resolve,所以立即將回調(diào)放入微任務(wù)隊(duì)列(結(jié)論1)

主線程:外2then

微任務(wù)隊(duì)列:外1then 的回調(diào)

但是由于此時(shí)這個(gè)回調(diào)還未執(zhí)行,所以外1then 返回的 promise 仍為 pending 狀態(tài)(結(jié)論2第二條),繼續(xù)同步執(zhí)行外2then,由于前面的 promise 是 pending 狀態(tài),所以外2then 的回調(diào)也不會(huì)被推入微任務(wù)隊(duì)列也不會(huì)執(zhí)行(結(jié)論2案例)

主線程:空

微任務(wù)隊(duì)列:外1then 的回調(diào)

當(dāng)主線程執(zhí)行完畢后,執(zhí)行微任務(wù),也就是外1then 的回調(diào),回調(diào)中首先打印log: 外部第一個(gè)then

隨后實(shí)例化內(nèi)部 promise,在實(shí)例化時(shí)執(zhí)行函數(shù),打印 log: 內(nèi)部promise,然后執(zhí)行 resolve 函數(shù)(結(jié)論1),接著執(zhí)行到內(nèi)部的第一個(gè) then(內(nèi)1then),由于前面的 promise 已被 resolve,所以將回調(diào)放入微任務(wù)隊(duì)列中(結(jié)論1)

主線程:內(nèi)2then

微任務(wù)隊(duì)列:內(nèi)1then 的回調(diào)

由于正在執(zhí)行外1then 的回調(diào),所以外1then 返回的 promise 仍是 pending 狀態(tài),外2then 的回調(diào)仍不會(huì)被注冊(cè)也不會(huì)被執(zhí)行

接著同步執(zhí)行內(nèi)2then,由于它前面的 promise (內(nèi)1then 返回的 promise) 是 pending 狀態(tài)(因?yàn)閮?nèi)1then 的回調(diào)在微任務(wù)隊(duì)列中,還未執(zhí)行),所以內(nèi)2then 的回調(diào)和外2then 的回調(diào)一樣,不注冊(cè)不執(zhí)行(結(jié)論2案例)

主線程:空

微任務(wù)隊(duì)列:內(nèi)1then 的回調(diào)

此時(shí)外1then 的回調(diào)全部執(zhí)行完畢,外1then 返回的 promise 的狀態(tài)由 pending 變?yōu)?resolved(結(jié)論2第二條),同時(shí)遍歷之前通過(guò) then 給這個(gè) promise 注冊(cè)的所有回調(diào),將它們的回調(diào)放入微任務(wù)隊(duì)列中(結(jié)論2),也就是外2then 的回調(diào)

主線程:空

微任務(wù)隊(duì)列:內(nèi)1then 的回調(diào),外2then 的回調(diào)

此時(shí)主線程邏輯執(zhí)行完畢,取出第一個(gè)微任務(wù)執(zhí)行

主線程:內(nèi)1then 的回調(diào)

微任務(wù)隊(duì)列:外2then 的回調(diào)

執(zhí)行內(nèi)1then 的回調(diào)打印 log: 內(nèi)部第一個(gè)then,回調(diào)執(zhí)行完畢后,內(nèi)1then 返回的 promise 由 pending 變?yōu)?resolved(結(jié)論2第二條),同時(shí)遍歷之前通過(guò) then 給這個(gè) promise 注冊(cè)的所有回調(diào),將它們的回調(diào)放入微任務(wù)隊(duì)列中(結(jié)論2),也就是內(nèi)2then 的回調(diào)

主線程:空

微任務(wù)隊(duì)列:外2then 的回調(diào),內(nèi)2then 的回調(diào)

執(zhí)行外2then 的回調(diào)打印 log: 外部第二個(gè)then,回調(diào)執(zhí)行完畢,外2then 返回的 promise 由 pending 變?yōu)?resolved(結(jié)論2第二條),同時(shí)遍歷之前通過(guò) then 給這個(gè) promise 注冊(cè)的所有回調(diào),將它們放入微任務(wù)隊(duì)列中(結(jié)論2)

這時(shí)由于外2 then 返回的 promise 沒(méi)有再進(jìn)一步的鏈?zhǔn)秸{(diào)用了,主線程任務(wù)結(jié)束

主線程:空

微任務(wù)隊(duì)列:內(nèi)2then 的回調(diào)

接著取出微任務(wù),執(zhí)行內(nèi)2then 的回調(diào)打印 log: 內(nèi)部第二個(gè)then,內(nèi)2then 返回的 promise 的狀態(tài)變?yōu)?resolved(結(jié)論2第二條),同時(shí)遍歷之前通過(guò) then 給這個(gè) promise 注冊(cè)的所有回調(diào)(沒(méi)有),至此全部結(jié)束

參考資料

我自己寫的 promise~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 弄懂js異步 講異步之前,我們必須掌握一個(gè)基礎(chǔ)知識(shí)-event-loop。 我們知道JavaScript的一大特點(diǎn)...
    DCbryant閱讀 2,892評(píng)論 0 5
  • Promise 對(duì)象 Promise 的含義 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,844評(píng)論 1 56
  • JS的運(yùn)行機(jī)制 先來(lái)一個(gè)今日頭條的面試題 1. 單線程的JavaScript js是單線程的,基于事件循環(huán),非阻塞...
    行動(dòng)派巨人閱讀 23,515評(píng)論 10 38
  • 火車上走廊里有一對(duì)母女,應(yīng)該是媽媽帶著女兒回姥姥家,她倆站在走廊里看著窗外的風(fēng)景。 “媽媽,外面的房子好破啊?!毙?..
    檸檬和咖啡閱讀 205評(píng)論 0 0
  • 【究竟想成為怎樣的老師】天網(wǎng)計(jì)算機(jī)學(xué)校
    劉大帥54564閱讀 244評(píng)論 0 0

友情鏈接更多精彩內(nèi)容