Bob Cheng
Web

Javascript Event Loop

Javascript Engine and Runtime

Engine 用來編譯和執行 Javascript,而 Runtime 則是提供完整的環境讓 engine 執行。 舉例來說:

  • google chrome 的 runtime 使用 V8 engine 並提供 web API 讓 engine 可以操作 DOM 等
  • nodejs 同樣使用 V8 engine 並提供 fs 等模組讓 engine 可以和文件互動等

event loop 就是由 runtime 提供的一個功能,讓原本是 single-threaded language 的 Javascript 可以「看起來像」執行 asynchronous 的操作。

Runtime and Event Loop

javascript runtime 架構

  • 每次有 function 被呼叫時,就會建立該 function 的 frame (包含它的參數、變數等),並 push 到 stack 裡面執行,當 return 時才會被 pop 出來。
  • 當呼叫到 Web API 時,還是會建立 frame,不過馬上 return 交給 runtime 來去執行 (所以此時 stack 裡面已經沒有該 frame),當 runtime 執行完後會呼叫 callback function,並將其放入 task queue (或是 microtask queue)。
  • 每當 stack 清空後,event loop 就會將 task queue 的第一個 task 移到 stack 內去執行。
    • 雖然說是 queue,不過實際上並非 FIFO,大多數的瀏覽器都會對 task 做 priority
  • 每當一個 task (marcotask) 被執行完後,會先將 microtask queue 內的所有 task 都放到 stack 執行完後接著執行 rendering,結束後才繼續執行其他的 task
    • microtask queue 是 FIFO

Marcotask and Microtask

microtask 包含:

  • promise.then/catch/finally 的 callback function (promise 內的程式不會)
  • async 內的第一個 await 之後的程式
  • MutationObserver()
  • queueMicrotask() marcotask 其實就是一般的 task

microtask 和 microtask queue 的出現有一部份是為了因應 promise (ES2015) 的出現,因為 promise 在 callback 的時候需要確保執行的順序以及上下文。 舉例來說

console.log("Start");
setTimeout(() => console.log("setTimeout"), 0);
Promise.resolve().then(() => console.log("Promise callback"));
console.log("End");

如果沒有 microtask queue 的話,輸出可能會是 Start -> End -> setTimeout -> Promise callback,也就是說 .then 的內容被放到了下一個 event loop 才執行,上下文可能已經不同造成邏輯錯誤。 同時與 DOM 相關的操作 (MutationObserver()) 也被納入 microtask 因為如果沒有在 render 前執行可能會導致 render 的結果不一致。


References