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

image

前言

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

image

憑借我對 Promise 源碼的了解耕渴,這種問題能難住我?

image

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

image

之后再次翻閱了一遍曾經(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

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

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

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

結(jié)論1

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

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

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

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

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

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

結(jié)論2

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

如何理解通過 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í)行撩扒,也不會被放入微任務(wù)隊列中似扔,它們會被 p 內(nèi)部儲存起來,等到 p 被 resolve 后搓谆,依次將這 3 個回調(diào)推入微任務(wù)隊列炒辉,此時如果沒有同步任務(wù)就會逐個取出再執(zhí)行

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

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

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

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

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

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

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

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

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

image

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

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

解析

分析完 promise 和 then 的行為后避凝,我們結(jié)合代碼來解析問題(建議分屏舞萄,比對問題章節(jié)中的案例代碼查看解析)

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

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

主線程:外部第一個 then,外部第二個 then

微任務(wù)隊列:空

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

主線程:外2then

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

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

主線程:空

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

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

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

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

微任務(wù)隊列:內(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)(因?yàn)閮?nèi)1then 的回調(diào)在微任務(wù)隊列中镶柱,還未執(zhí)行)旷档,所以內(nèi)2then 的回調(diào)和外2then 的回調(diào)一樣,不注冊不執(zhí)行(結(jié)論2案例)

主線程:空

微任務(wù)隊列:內(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)放入微任務(wù)隊列中(結(jié)論2)查吊,也就是外2then 的回調(diào)

主線程:空

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

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

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

微任務(wù)隊列:外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)放入微任務(wù)隊列中(結(jié)論2)炼杖,也就是內(nèi)2then 的回調(diào)

主線程:空

微任務(wù)隊列:外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)罚缕,將它們放入微任務(wù)隊列中(結(jié)論2)

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

主線程:空

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

接著取出微任務(wù),執(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é)束

參考資料

我自己寫的 promise~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腌乡,一起剝皮案震驚了整個濱河市盟劫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌与纽,老刑警劉巖侣签,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異急迂,居然都是意外死亡影所,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門袋毙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來型檀,“玉大人,你說我怎么就攤上這事听盖≌湍纾” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵皆看,是天一觀的道長仓坞。 經(jīng)常有香客問我,道長腰吟,這世上最難降的妖魔是什么无埃? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮毛雇,結(jié)果婚禮上嫉称,老公的妹妹穿的比我還像新娘。我一直安慰自己灵疮,他們只是感情好织阅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著震捣,像睡著了一般荔棉。 火紅的嫁衣襯著肌膚如雪闹炉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天润樱,我揣著相機(jī)與錄音渣触,去河邊找鬼。 笑死壹若,一個胖子當(dāng)著我的面吹牛嗅钻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播店展,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼啊犬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了壁查?” 一聲冷哼從身側(cè)響起觉至,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎睡腿,沒想到半個月后语御,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡席怪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年应闯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挂捻。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡碉纺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刻撒,到底是詐尸還是另有隱情骨田,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布声怔,位于F島的核電站态贤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏醋火。R本人自食惡果不足惜悠汽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芥驳。 院中可真熱鬧柿冲,春花似錦、人聲如沸兆旬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至慨亲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宝鼓,已是汗流浹背刑棵。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留愚铡,地道東北人蛉签。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像沥寥,于是被迫代替她去往敵國和親碍舍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

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