← JavaScript Learning Path

JAVASCRIPT V2 · RUNTIME · 26

Event Loop:為什麼 Promise、Timer、Click 的順序不能只看程式碼位置?

42 教你使用 Promise;這一課解釋 Promise continuation 為什麼會在那個時間點執行。核心模型是 call stack → task → microtask checkpoint → render opportunity → next task。

Learning outcomes

1. Why now:async code 能跑,不代表你能預測它

console.log("A");

setTimeout(
  () => console.log("B"),
  0
);

Promise.resolve()
  .then(
    () => console.log("C")
  );

console.log("D");

如果只靠「從上到下」,你會猜錯。正確答案是 A D C B。

2. Mental model:一輪 event loop

take one task
  ↓
run JavaScript until stack empty
  ↓
drain microtasks
  ↓
browser may render
  ↓
take next task

這是學習用的最小模型。實際 Browser event loop 規範更細,但這個模型足以解釋大多數前端 ordering bug。

3. Call Stack:目前正在跑什麼

function outer() {
  inner();
}

function inner() {
  console.log("inside");
}

outer();
stack:
global
  ↓ outer
  ↓ inner
  ↑ return
  ↑ return
empty

Event loop 不會在任意一行中間把另一個普通 callback 插進 stack。

4. Task:timer、UI event 等待下一輪機會

console.log("A");
setTimeout(
  () => console.log("B"),
  0
);
console.log("C");

目前 task 先完整輸出 A/C;timer callback 至少等下一個可執行 task opportunity。

5. Microtask:Promise continuation 的優先 checkpoint

console.log("A");

Promise.resolve()
  .then(() => console.log("B"));

setTimeout(
  () => console.log("C"),
  0
);

console.log("D");

// A D B C

同步 stack 清空後,Browser 會先處理 microtasks,再進下一個 timer task。

6. Execution trace:逐步不要跳

stepstack / queue changeoutput
1run console AA
2register Promise continuation → microtask later—
3register timer → task later—
4run console DD
5stack empty → drain microtasksB
6next taskC

7. await continuation 也是 Promise scheduling

async function demo() {
  console.log("inside 1");

  await Promise.resolve();

  console.log("inside 2");
}

console.log("start");
demo();
console.log("end");

預測:

start
inside 1
end
inside 2

await 前半段同步執行;後半段 continuation 等 Promise settle 後排入 microtask。

8. Worked Example A:microtask 可以新增 microtask

Promise.resolve()
  .then(() => {
    console.log("A");

    queueMicrotask(
      () => console.log("B")
    );
  })
  .then(() => {
    console.log("C");
  });

Microtask queue 在 checkpoint 中持續 drain;新加入的 microtask 也可能在 render/next task 之前執行。

9. Worked Example B:Loading 為什麼畫不出來?

status.textContent =
  "Loading";

const end =
  performance.now() + 3000;

while (
  performance.now() < end
) {
  // block main thread
}

status.textContent =
  "Done";

雖然 DOM value 曾變成 Loading,但 Browser 可能一直沒有 render opportunity。長同步 task 結束時狀態已變 Done。

10. Worked Example C:microtask starvation

function again() {
  queueMicrotask(again);
}

again();

如果 microtask 無限新增自己,checkpoint 永遠 drain 不完,next task 與 render 都可能被餓死。Async 不代表一定 responsive。

11. Browser 與 host environment 的責任

JavaScript engine
→ execute JS / stack / Promise jobs

Browser host
→ timers / network / DOM events / rendering

Event loop
→ coordinate when runnable JS gets a turn

說「JavaScript 是單執行緒」時,最好限定為主要 JS execution stack;Browser 本身當然不只做一件事。

12. Common mistakes

A. 0ms = immediate

錯。至少等 current task 完成。

B. Promise = background thread

Promise 是 future result/scheduling abstraction,不是 worker thread。

C. await 之前與之後都完全 asynchronous

async function 呼叫後會先同步跑到第一個真正 suspend point。

D. 大量 microtask 一定比 task「更快更好」

濫用 microtask 會延後 render 與其他 task。

13. Debug evidence:卡頓要找 long task,不是先怪 API

當 click 有反應但畫面幾秒不更新,可以蒐集:

如果 Network 50ms 完成、main thread 卻 busy 2500ms,瓶頸不在 server。

14. Guided exercise:預測 ordering

console.log("1");

setTimeout(
  () => console.log("2"),
  0
);

Promise.resolve()
  .then(
    () => console.log("3")
  );

console.log("4");

要求:先寫 output,再畫 task/microtask queue,再執行驗證。

15. Independent exercise:加入 await

設計一段包含:同步 log、await Promise.resolve()、setTimeout(...,0) 的程式。先預測至少 5 個 log 的順序,再執行。

16. Challenge:兩層 microtask + timer

console.log("A");

Promise.resolve()
  .then(() => {
    console.log("B");

    Promise.resolve()
      .then(
        () => console.log("C")
      );
  });

setTimeout(
  () => console.log("D"),
  0
);

console.log("E");

不要只猜答案;寫出每一步 queue 如何改變。

Project checkpoint:Grade Tracker timing diagram

click task
  ↓
set loading state
  ↓
start fetch
  ↓
stack empty
  ↓
microtasks
  ↓
render opportunity
  ↓
Loading visible
  ↓
network completes
  ↓
Promise continuation microtask
  ↓
state + render results

17. Vocabulary / Summary

18. Further reading

Knowledge check · Mastery

  1. 預測 sync + Promise + timer 的順序。
  2. 解釋 await 前後為何分屬不同 execution phase。
  3. 說明 long task 與 microtask starvation 對 render 的影響。
  4. 完成頁面下方 Execution Trace mastery。