Promise 你真的用明白了麼?

文章首發自筆者的 Github前端

Promise 關於 API 這塊你們應該都能熟練使用,可是和微任務相關的你可能還存在知識盲區。git

前置知識

在開始正文前,咱們先把本文涉及到的一些內容提早定個基調。github

Promise 哪些 API 涉及了微任務?

Promise 中只有涉及到狀態變動後才須要被執行的回調纔算是微任務,好比說 thencatchfinally ,其餘全部的代碼執行都是宏任務(同步執行)。數組

上圖中藍色爲同步執行,黃色爲異步執行(丟到微任務隊列中)。promise

這些微任務什麼時候被加入微任務隊列?

這個問題咱們根據 ecma 規範來看:微信

  • 若是此時 Promise 狀態爲 pending,那麼成功或失敗的回調會分別被加入至 [[PromiseFulfillReactions]][[PromiseRejectReactions]] 中。若是你看過手寫 Promise 的代碼的話,應該能發現有兩個數組存儲這些回調函數。
  • 若是此時 Promise 狀態爲非 pending 時,回調會成爲 Promise Jobs,也就是微任務。

瞭解完以上知識後,正片開始。異步

同一個 then,不一樣的微任務執行

初級

Promise.resolve()
  .then(() => {
    console.log("then1");
    Promise.resolve().then(() => {
      console.log("then1-1");
    });
  })
  .then(() => {
    console.log("then2");
  });

以上代碼你們應該都能得出正確的答案:then1 → then1-1 → then2函數

雖然 then 是同步執行,而且狀態也已經變動。但這並不表明每次遇到 then 時咱們都須要把它的回調丟入微任務隊列中,而是等待 then 的回調執行完畢後再根據狀況執行對應操做。優化

基於此,咱們能夠得出第一個結論:鏈式調用中,只有前一個 then 的回調執行完畢後,跟着的 then 中的回調纔會被加入至微任務隊列。lua

中級

你們都知道了 Promise resolve 後,跟着的 then 中的回調會立刻進入微任務隊列。

那麼如下代碼你認爲的輸出會是什麼?

let p = Promise.resolve();

p.then(() => {
  console.log("then1");
  Promise.resolve().then(() => {
    console.log("then1-1");
  });
}).then(() => {
  console.log("then1-2");
});

p.then(() => {
  console.log("then2");
});

按照一開始的認知咱們不可貴出 then2 會在 then1-1 後輸出,可是實際狀況倒是相反的。

基於此咱們得出第二個結論:每一個鏈式調用的開端會首先依次進入微任務隊列。

接下來咱們換個寫法:

let p = Promise.resolve().then(() => {
  console.log("then1");
  Promise.resolve().then(() => {
    console.log("then1-1");
  });
}).then(() => {
  console.log("then2");
});

p.then(() => {
  console.log("then3");
});

上述代碼其實有個陷阱,then 每次都會返回一個新的 Promise,此時的 p 已經不是 Promise.resolve() 生成的,而是最後一個 then 生成的,所以 then3 應該是在 then2 後打印出來的。

順便咱們也能夠把以前得出的結論優化爲:同一個 Promise 的每一個鏈式調用的開端會首先依次進入微任務隊列。

高級

如下你們能夠猜猜 then1-2 會在什麼時候打印出來?

Promise.resolve()
  .then(() => {
    console.log("then1");
    Promise.resolve()
      .then(() => {
        console.log("then1-1");
        return 1;
      })
      .then(() => {
        console.log("then1-2");
      });
  })
  .then(() => {
    console.log("then2");
  })
  .then(() => {
    console.log("then3");
  })
  .then(() => {
    console.log("then4");
  });

這題確定是簡單的,記住第一個結論就能得出答案,如下是解析:

  • 第一次 resolve 後第一個 then 的回調進入微任務隊列並執行,打印 then1
  • 第二次 resolve 後內部第一個 then 的回調進入微任務隊列,此時外部第一個 then 的回調所有執行完畢,須要將外部的第二個 then 回調也插入微任務隊列。
  • 執行微任務,打印 then1-1then2,而後分別再將以後 then 中的回調插入微任務隊列
  • 執行微任務,打印 then1-2then3 ,以後的內容就不一一說明了

接下來咱們把 return 1 修改一下,結果可就大不相同啦:

Promise.resolve()
  .then(() => {
    console.log("then1");
    Promise.resolve()
      .then(() => {
        console.log("then1-1");
        return Promise.resolve();
      })
      .then(() => {
        console.log("then1-2");
      });
  })
  .then(() => {
    console.log("then2");
  })
  .then(() => {
    console.log("then3");
  })
  .then(() => {
    console.log("then4");
  });

當咱們 return Promise.resolve() 時,你猜猜 then1-2 會什麼時候打印了?

答案是最後一個才被打印出來。

爲何在 then 中分別 return 不一樣的東西,微任務的執行順序竟有如此大的變化?如下是筆者的解析。

PS:then 返回一個新的 Promise,而且會用這個 Promise 去 resolve 返回值,這個概念須要你們先了解一下。

根據 Promise A+ 規範

根據規範 2.3.2,若是 resolve 了一個 Promise,須要爲其加上一個 thenresolve

if (x instanceof MyPromise) {
  if (x.currentState === PENDING) {
  } else {
    x.then(resolve, reject);
  }
  return;
}

上述代碼節選自手寫 Promise 實現。

那麼根據 A+ 規範來講,若是咱們在 then 中返回了 Promise.resolve 的話會多入隊一次微任務,可是這個結論仍是與實際不符的,所以咱們還須要尋找其餘權威的文檔。

根據 ECMA - 262 規範

根據規範 25.6.1.3.2,當 Promise resolve 了一個 Promise 時,會產生一個NewPromiseResolveThenableJob,這是屬於 Promise Jobs 中的一種,也就是微任務。

This Job uses the supplied thenable and its then method to resolve the given promise. This process must take place as a Job to ensure that the evaluation of the then method occurs after evaluation of any surrounding code has completed.

而且該 Jobs 還會調用一次 then 函數來 resolve Promise,這也就又生成了一次微任務。

這就是爲何會觸發兩次微任務的來源。

最後

文章到這裏就完結了,你們有什麼疑問均可以在評論區提出。

推薦關注個人微信公衆號【前端真好玩】,工做日推送高質量文章。

image.png

筆者就任於酷家樂,家裝設計行業獨角獸。一流的可視化、前端技術團隊,有興趣的能夠簡歷投遞至 zx597813039@gmail.com
相關文章
相關標籤/搜索