Javascript Event Loop
這個題目是很基本的概念,但前端就喜歡創造新名詞複雜這件事情,筆記一下不然每次看到新名詞就要追
關鍵名詞
名詞 | 描述 | 範例 |
---|---|---|
call stack | 程式執行堆疊,每種程式語言都是一樣的模式,因為本質就是用來追蹤和管理函式呼叫的執行順序,這點可以載 throw error 時的錯誤訊息堆疊看得出來 | |
Web APIs | 瀏覽器支援非同步機制,會在背景執行不會阻斷 main thread,當他執行完畢會放進 Task queue 中等待 event loop 呼叫 | setTimeout/setInterval, fetch, XMLHttpRequest, DOM events |
Task queue | 又稱為 Marcotask queue, 跟 Microtask 做區別 | 同 Web APIs |
Microtask queue | 是比較特別的類型,會在 Marcotask queue dequeue 後被執行,因此其執行順序永遠是在 Marcotask 的前面,所有 Microtask 會在下一個 Marcotask 開始執行前及頁面重新渲染前執行完畢 | promise 的 then/catch/finally, queueMicrotask(func) |
pseudo code
ref: https://github.com/atotic/event-loop#event-loop-description
節錄自上述的 pseudo code 執行的區塊
while(true) {
task = eventLoop.nextTask();
if (task) {
task.execute();
}
eventLoop.executeMicrotasks();
if (eventLoop.needsRendering())
eventLoop.render();
}
可以看到這裡基本上就做三件事,這邊就是執行的核心觀念
- deque task queue
- deque & execute microtask
- render 也就是前述的執行順序:micro -> marco -> render 循環
範例
理解之後再看以下的例子
console.log('開始');
setTimeout(() => {
console.log('Macro task 1 (setTimeout)');
Promise.resolve().then(() => {
console.log('Micro task 在 Macro task 1 中');
});
}, 0);
Promise.resolve().then(() => {
console.log('Micro task 1');
});
setTimeout(() => {
console.log('Macro task 2 (setTimeout)');
}, 0);
console.log('結束');
// 開始
// 結束
// Micro task 1
// Marco task 1 (setTimeout)
// Micro task 在 Marco task 1 中
// Marco task 2 (setTimeout)
解釋
- 同步程式碼的執行
- 首先執行 console.log(‘開始’)
- 然後遇到第一個 setTimeout(Macro task 1),將它排入 Macro task 佇列
- 接著遇到 Promise(Micro task 1),將它排入 Micro task 佇列
- 遇到第二個 setTimeout(Macro task 2),將它排入 Macro task 佇列
- 最後執行 console.log(‘結束’)
- 第一輪事件循環
- 同步程式碼執行完畢後,檢查 Micro task 佇列
- 發現 Micro task 佇列中有 “Micro task 1”
- 執行它,輸出 console.log(‘Micro task 1’)
- 第二輪事件循環
- Micro task 佇列清空後,檢查 Macro task 佇列
- 執行第一個 setTimeout 回調(Macro task 1)
- 輸出 console.log(‘Macro task 1 (setTimeout)’)
- 在這個 Macro task 中產生了新的 Promise(Micro task)
- 根據規則,必須立即處理這個 Micro task
- 輸出 console.log(‘Micro task 在 Macro task 1 中’)
- 第三輪事件循環
- 檢查 Macro task 佇列
- 執行第二個 setTimeout 回調(Macro task 2)
- 輸出 console.log(‘Macro task 2 (setTimeout)’)
那假如 Promise 沒有接 resolve
會發生什麼事?
console.log('開始');
setTimeout(() => {
console.log('Macro task 1 (setTimeout)');
new Promise(() => {
console.log('Micro task 在 Macro task 1 中');
});
}, 0);
new Promise(() => {
console.log('Micro task 1');
});
setTimeout(() => {
console.log('Macro task 2 (setTimeout)');
}, 0);
console.log('結束');
答案如下
開始
Micro task 1
結束
Macro task 1 (setTimeout)
Micro task 在 Macro task 1 中
Macro task 2 (setTimeout)
可以看出new Promise
是直接同步執行,重要的是要理解
- new Promise(executor) 中的 executor 函數是同步執行的
- 如果 Promise 沒有被 resolve 或 reject,那麼這些回調永遠不會執行,只會 pending
最後推薦一部被推到爛的影片,很好地描述 event loop 與 call stack, web API 之間的關係
發佈時間
2025-2-21