JavaScript 是單 Thread 的

JavaScript 的引擎(V8、SpiderMonkey)只有一個 thread 執行 JS code。這代表:

  • 沒有 race condition(只有一個 thread,沒有「同時修改」)
  • CPU 密集型任務會阻塞一切(排序 10 萬筆資料時,UI 凍結)
  • I/O 操作用非同步(不能等 DB 回應——那樣的話整個 server 就卡死了)

Event Loop 的工作機制

Call Stack              Task Queue
┌──────────┐            ┌─────────────┐
│ current  │            │ setTimeout  │
│ function │ ←──────── │ click event │
└──────────┘            └─────────────┘
     ↑                        ↑
     │              Microtask Queue
     │              ┌─────────────┐
     │              │ Promise.then│
     └──────────── │ queueMicro..│
                    └─────────────┘

Event Loop 邏輯:
1. 執行 Call Stack 上的所有 code
2. Call Stack 空了 → 先清空 Microtask Queue(全部執行完)
3. 從 Task Queue 取一個 task 執行
4. 回到 1

Microtask(高優先級,stack 清空後立刻執行):

  • Promise.then / .catch / .finally
  • queueMicrotask()
  • MutationObserver

Macrotask / Task(下一輪 Event Loop):

  • setTimeout / setInterval
  • I/O callbacks
  • requestAnimationFrame(瀏覽器)

執行順序測驗

console.log('1');
 
setTimeout(() => console.log('2'), 0);
 
Promise.resolve().then(() => console.log('3'));
 
console.log('4');
 
// 輸出:1 → 4 → 3 → 2
// 解釋:
// 1, 4:同步執行
// 3:Promise.then 是 microtask,在 macrotask 之前
// 2:setTimeout 是 macrotask,最後執行

async/await 的本質

async/await 是 Promise 的語法糖——讓你用看起來同步的方式寫非同步邏輯:

// Promise 風格
function fetchUserData(id) {
    return fetch(`/api/users/${id}`)
        .then(res => res.json())
        .then(user => fetch(`/api/orders/${user.orderId}`))
        .then(res => res.json());
}
 
// async/await 風格(同樣的邏輯)
async function fetchUserData(id) {
    const userRes = await fetch(`/api/users/${id}`);
    const user = await userRes.json();
    const orderRes = await fetch(`/api/orders/${user.orderId}`);
    return orderRes.json();
}

await 暫停當前 async function 的執行,但不阻塞 thread——它讓出執行權給 Event Loop,等 Promise resolve 後恢復執行(透過 microtask queue)。


和其他語言的對比

Python asyncio:和 JavaScript 類似,單 thread 的 event loop,async/await 語法糖包裝 coroutine。asyncio.gather() 並發多個 coroutine。

Go goroutine:更接近 OS thread 但更輕量,Go runtime 自動在多個 OS thread 上調度 goroutine,適合 CPU 密集型也適合 I/O 密集型。

Java virtual thread(JDK 21+):類似 Go goroutine,用同步語法寫,底層 JVM 自動非阻塞。


為什麼 CPU 密集型任務要放 Worker

在 Node.js 裡做大量 JSON 解析或圖片處理——這些是 CPU 密集型,會佔用 Event Loop 幾百毫秒,在這段時間裡所有其他請求都要等。

解法:worker_threads(Node.js)、ProcessPoolExecutor(Python)——在獨立的 thread / process 裡做 CPU 密集型工作,不佔用 main thread 的 Event Loop。