ข้ามไปยังเนื้อหา

Microtasks & Macrotasks

event loop ประมวลผล callback แบบ async จากสองคิวที่แยกกันด้วยลำดับความสำคัญต่างกัน:

Microtask queue — ประมวลผลหลังจากทุก task ก่อน macrotask ถัดไป แหล่งที่มา:

  • callback ของ Promise .then / .catch / .finally
  • queueMicrotask(fn)

Macrotask queue (หรือเรียก task queue) — ประมวลผลทีละหนึ่ง task ต่อรอบ event loop แหล่งที่มา:

  • callback ของ setTimeout
  • callback ของ setInterval
  • callback ของ I/O (network, file system)

กฎง่าย ๆ: หลังแต่ละ macrotask ทำเสร็จ เอนจินจะระบาย microtask queue ทั้งหมด ก่อนจะหยิบ macrotask ถัดไป

┌─────────────────────────────┐
│ Execute one macrotask │
│ (or the initial script) │
└──────────────┬──────────────┘
┌─────────────────────────────┐
│ Drain all microtasks │◄──┐
│ (including newly added) │ │ new microtasks
└──────────────┬──────────────┘───┘
┌─────────────────────────────┐
│ Pick next macrotask │
└─────────────────────────────┘

Node.js เพิ่ม API การจัดคิวอีกสองตัวที่ต้องใช้ Node runtime:

// ใช้ได้ใน Node.js เท่านั้น — ไม่ได้ในเบราว์เซอร์
process.nextTick(function() {
console.log('nextTick fires before other microtasks');
});
setImmediate(function() {
console.log('setImmediate fires after I/O callbacks in the current loop iteration');
});

callback ของ process.nextTick ถูกระบายก่อน microtask queue ส่วน setImmediate ทำงานในเฟส check ของ event loop ของ Node เนื่องจากทั้งสองต้องใช้ Node runtime จึงใช้ไม่ได้ในตัวรันใน browser ด้านล่าง — ให้รันในเครื่องหรือใน StackBlitz

เดโมนี้พิมพ์สี่บรรทัด ลองทายลำดับก่อนรัน:

  1. console.log แบบ synchronous ทำงานทันทีบน call stack
  2. setTimeout(..., 0) จัดคิว macrotask
  3. Promise.resolve().then(...) จัดคิว microtask (Promise resolve อยู่แล้ว reaction จึงถูกจัดคิวแบบ synchronous)
  4. queueMicrotask(...) จัดคิวอีก microtask — หลังจาก reaction ของ Promise ที่จัดคิวไปข้างบนแล้ว

หลังสคริปต์ synchronous ทำเสร็จ microtask ทั้งหมดจะระบายตามลำดับ แล้ว macrotask ค่อยทำงาน

JavaScript

ผลลัพธ์จะเป็นลำดับนี้เสมอ:

  • 1 sync — call stack
  • 2 microtask (Promise) — microtask ตัวแรกที่จัดคิว
  • 3 microtask (queueMicrotask) — microtask ตัวที่สองที่จัดคิว
  • 4 macrotask — ทำงานหลังจาก microtask queue ว่างแล้วเท่านั้น
จากโค้ดนี้ — อะไรพิมพ์ก่อน: callback ของ Promise .then หรือ callback ของ setTimeout?
queueMicrotask(fn) ทำอะไร?
ถ้า callback ของ microtask เรียก queueMicrotask เอง microtask ใหม่นั้นทำงานเมื่อใด?