JS 原力覺醒 Day15 - Macrotask 與 MicroTask

上一篇針對 Promise 的語法做了一個基本的解說,但其實今天的內容才是我想講的,Promise 的運作邏輯不難理解,但若是 Promise 在整個 JS 以及瀏覽器裡的流程可能就比較複雜了,現在我們都知道幾件事情:

  • 一個 Promise 最終會有兩種狀態
  • 對應 Promise 的不同狀態,會各自觸發 .then 與 .catch 兩個函式
  • 利用 Promise 可以達成非同步行為,而且內容可以自訂

而雖然在上一章節一直提到非同步,但是對於 Promise 裡所謂非同步執行的部分,目前我們還是沒有很明確的解釋,到底是哪一部分會以非同步的方式被執行?以及什麼時候會執行?這是這篇文章想要探討跟說明的。

Outline

  • Tasks
  • Micro Tasks
  • Microtask 與 Macrotask 同時發生的例子

Macrotasks

我們在 Event Queue 章節裡面所提到 Web API 有些具有非同步的行為,而在非同步的目的達成之後,瀏覽器會把給定的對應的函式推送到 Event Queue 裡面,這些一個一個函式正好代表每一件要做的事情,因此在 JS 裡面,以「 Task 」或 「Macrotask 」來稱呼,為了避免混淆,以下將用 Macrotask Queue 來指稱之前提到的 Event Queue 。

https://ithelp.ithome.com.tw/upload/images/20190930/20106580UMeCNMZgKH.jpg

關於 Task 有兩個細節可以注意:

  • 以瀏覽器的角度來看,在每一個 Task 結束之前,不會有任何瀏覽器的 rending 產生
  • 如果一個 Task 執行所花的時間過長,那麼瀏覽器就無法執行其他的 Task ,所以過一段時間之後會提出「頁面沒有回應」的警告,建議你關閉這個分頁,這種情況你應該有遇過。

Microtasks

Microtask 通常由 Promise 產生,Promise 裡用到的 .then / .catch 函式會以非同步的方式來被執行,回想下 Queue 的概念,所以的非同步行為指的是,會在全域執行環境執行完之後才被執行,因此一但 Promise 的 callback 內容執行完成,狀態再也不是 pending 時,.then 或 .catch 的函式內容就會被推送到 Queue 裡面等待執行,這個被推送到 Queue 的函式就是 Microtask。

相對於管理 Web API 所屬事件的 Macrotask Queue ,Promise 產生的 Microtask 也有自己的 Queue ,在 JS 內被稱為 Job Queue 或 Microtask Queue,而 Job Queue 與 Event Queue 運作方式上有一點不一樣。

差在哪裡呢?在 Queue 裡面的每個 Macrotask 執行完畢後 ,就算 Event Queue 裡面還有其他的 Task,JS 引擎依舊會優先執行 Microtask Queue 裡面的所有 Task ,在這個同時也不會重新渲染網頁,換句話說,Microtask 的執行是穿插在每個 Macrotask 之間,兩者的差異也就在執行順序的不同而已。

https://ithelp.ithome.com.tw/upload/images/20190930/20106580a7zj27GtsT.jpg

Microtask 與 Macrotask 同時發生的例子

如果還是覺得很抽象,下面我會帶個例子,直接用程式碼來比較 Macrotask 與 Microtask 執行順序的不同,應該比較能夠讓你了解,看看下面的程式碼:

setTimeout(() => alert("timeout"));

Promise.resolve()
  .then(() => alert("promise"));

alert("global ex. context");

這段程式碼剛好同時用到 Web API 與 Promise ,各自在呼叫後會產生一個 Macrotask 以及 Microtask ,不過在順序上是哪個會先被執行呢?稍微分析一下:

  • 所有的 Queue 都會等待執行環境堆疊被清空,alert 肯定會先執行
  • setTimeout 對應的函式會被當作一個 Macrotask ,等待時間到之後被送入 Macrotask Queue
  • Promise 對應的 .then 或 .catch 的函式會被當作一個 Microtask 送入 Microtask Queue
  • 在執行環境堆疊清空之後,通常網頁會先做一次 Render,Render 的動作同時也算是一個 Macrotask

因此推測 alert 的順序應該會像是這樣:

  1. "global ex. contenxt"
  2. "timeout"
  3. "promise"

但是並不是!結果會是 "promise""timeout" 還要更先被 log 出來:

  1. "global ex. contenxt"
  2. "promise"
  3. "timeout"

這是為什麼呢?這邊可能會有點抽象,前面我們在分析 JS 語法與運作模式的時候,大多是從 JS 引擎的角度出發。而前面也有提到, Queue 的概念並不屬於 JS 引擎的一部分,相對的歸屬於瀏覽器。對於瀏覽器來說,在網頁頁面開啟時,載入對應的 JS 檔並且執行這件事情,也是一個 Macrotask 。

而剛剛提到 Macrotask 執行完畢後,會優先執行 Microtask ,因此你會看到 "promise" 出現的順序先於 "timeout"

https://ithelp.ithome.com.tw/upload/images/20190930/20106580BZxnDVGnKD.jpg

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×