Event Loop อธิบายแบบเห็นภาพ — ทำไม Promise ถึงมาก่อน setTimeout

เข้าใจ Call Stack, Microtask และ Macrotask แล้วคุณจะทายลำดับ console.log ได้ถูกทุกครั้ง

ลองดูโค้ดนี้แล้วทายว่าจะแสดงผลออกมาในลำดับไหน ถ้าคุณตอบว่า A B C บทความนี้เขียนมาเพื่อคุณครับ

quiz-me.js
setTimeout(() => console.log("A"), 0);
Promise.resolve().then(() => console.log("B"));
console.log("C");

คำตอบที่ถูกคือ C B A ครับ ถึงจะตั้ง setTimeout ไว้ 0 มิลลิวินาที มันก็ยังมาทีหลังสุด เหตุผลอยู่ที่สิ่งที่เรียกว่า Event Loop

JavaScript ทำได้ทีละอย่าง

JavaScript ในเบราว์เซอร์ทำงานบน thread เดียว แปลว่าในแต่ละช่วงเวลารันโค้ดได้แค่ชุดเดียว งานที่ต้องรอ เช่น ตั้งเวลา โหลดข้อมูล หรือรอผู้ใช้คลิก จึงถูกส่งออกไปให้เบราว์เซอร์จัดการ แล้วค่อยกลับเข้ามาเมื่อพร้อม

คำถามคือ “กลับเข้ามาตอนไหน และเรียงลำดับยังไง” ซึ่งคำตอบมีอยู่ 3 ส่วน

  • Call Stack — โค้ดที่กำลังรันอยู่ตอนนี้
  • Microtask Queue — คิวของ Promise.then, queueMicrotask, await
  • Macrotask Queue (Task Queue) — คิวของ setTimeout, setInterval, event ต่างๆ

กฎของ Event Loop

Event Loop ทำงานวนซ้ำตามกฎง่ายๆ แค่นี้

  1. รันโค้ดใน Call Stack จนว่าง
  2. รัน Microtask ทั้งหมด ที่อยู่ในคิว (ถ้ามีงานใหม่เพิ่มเข้ามา ก็รันต่อจนหมด)
  3. หยิบ Macrotask มา 1 งาน แล้วกลับไปข้อ 1
จำง่ายๆMicrotask คือ “งานด่วน” ต้องเคลียร์ให้หมดทุกครั้งก่อนจะหยิบ Macrotask ซึ่งเป็น “งานปกติ” มาทำทีละงาน

ดูทีละขั้น

กด “ขั้นต่อไป” เพื่อดูว่าโค้ดตัวอย่างด้านบนเดินทางผ่านแต่ละคิวยังไง

Call Stack
Microtask
Macrotask
กด “ขั้นต่อไป” เพื่อเริ่ม
console:
0/7

แล้ว async/await ล่ะ?

await เป็นแค่ Promise ในอีกรูปแบบหนึ่ง โค้ดที่อยู่ หลัง await จะถูกส่งเข้า Microtask Queue เหมือน .then()

await.js
async function run() {
  console.log("1");
  await null;
  console.log("3"); // ← เข้า microtask
}
run();
console.log("2");
กับดักที่เจอบ่อยถ้าเขียน Promise ที่สร้าง Promise ใหม่ต่อกันไปเรื่อยๆ Microtask Queue จะไม่มีวันว่าง ทำให้หน้าเว็บค้าง เพราะ Event Loop ไม่ได้ไปทำงานอื่นเลย รวมถึงการวาดหน้าจอด้วย

ลองรันเอง

แก้โค้ดด้านล่างแล้วกด Run เพื่อดูลำดับจริง โค้ดรันอยู่ใน Web Worker แยกต่างหาก จึงไม่กระทบหน้าเว็บ

playground.js
กด Run เพื่อดูผลลัพธ์

สรุป

  • JavaScript รันโค้ดทีละชุดบน thread เดียว
  • โค้ดปกติรันก่อน → Microtask ทั้งหมด → Macrotask ทีละงาน
  • Promise.then และโค้ดหลัง await เป็น Microtask จึงมาก่อน setTimeout เสมอ
  • ระวังอย่าสร้าง Microtask ไม่รู้จบ เพราะหน้าเว็บจะค้าง

พร้อมแล้วก็ไปทำ Quiz ท้ายบทด้านล่างเพื่อรับ XP ได้เลยครับ

Quiz ท้ายบท

5 ข้อ · ข้อละ 10 XP

ทดสอบความเข้าใจเรื่อง Event Loop ตอบถูกข้อละ 10 XP

  • 5 ข้อ
  • ไม่จับเวลา
  • เฉลยทันทีทุกข้อ