ทำความเข้าใจว่าเอนจินจัดลำดับการทำงานของโค้ดแบบซิงโครนัส microtask และ macrotask อย่างไรจริง ๆ
เปิดบทเรียนนี้ใน KodokonJavaScript รันโค้ดของคุณบน เธรดเดียว แต่ event loop ไม่ได้ถูกกำหนดโดย ECMAScript แต่เป็นสเปกของ HTML (และ libuv ในฝั่ง Node.js) ที่อธิบายว่ามันทำงานอย่างไร เอนจินจะเก็บ call stack สำหรับโค้ดที่กำลังรันอยู่ คิวของ macrotask (timer, I/O, event) และคิวของ microtask (Promise.prototype.then, queueMicrotask, MutationObserver) กฎทองคือ หลังจากแต่ละ macrotask คิวของ microtask จะถูก ระบายจนหมดเกลี้ยง ก่อนที่จะคืนการควบคุมกลับไป
console.log("script start");
setTimeout(() => console.log("timeout"), 0);
Promise.resolve()
.then(() => console.log("promise 1"))
.then(() => console.log("promise 2"));
console.log("script end");
// script start, script end,
// promise 1, promise 2, timeoutสคริปต์เริ่มต้นเองก็เป็น macrotask หนึ่ง กล่าวคือ จนกว่ามันจะทำงานเสร็จ อย่างอื่นจะยังไม่รัน เมื่อ call stack ว่างเปล่า เอนจินจะระบายคิวของ microtask รวมถึง microtask ที่ถูกเพิ่มเข้ามาระหว่างการระบายด้วย รายละเอียดนี้อธิบายว่าทำไมการเรียก then สองครั้งที่ต่อกันจึงรันก่อน setTimeout(fn, 0) เพราะแต่ละ then จะจัดคิว microtask อีกตัวหนึ่ง และการควบคุมจะไม่ถูกคืนกลับไปจนกว่าคิวจะว่างเปล่า
setTimeout(() => console.log("macro 1"), 0);
setTimeout(() => console.log("macro 2"), 0);
queueMicrotask(() => {
console.log("micro 1");
queueMicrotask(() => console.log("micro 2"));
});
// micro 1, micro 2, macro 1, macro 2คีย์เวิร์ด await สร้างขึ้นบนกลไกนี้ มันจะ หยุดพัก ฟังก์ชัน async คืนการควบคุมกลับไปยังผู้เรียก แล้วจัดคิวการทำงานต่อในรูปของ microtask แม้แต่ await null ก็ทริกเกอร์การหยุดพักนี้ เพราะค่าจะถูกห่อไว้ใน promise ที่ resolve แล้ว นั่นคือเหตุผลว่าทำไมโค้ดที่วางไว้หลัง await จึงไม่รันแบบซิงโครนัส
async function main() {
console.log("before await");
await null;
console.log("after await");
}
main();
console.log("sync code");
// before await, sync code, after awaitC แบบซิงโครนัส จัดคิว A ผ่าน setTimeout(fn, 0) และ B ผ่าน Promise.resolve().then ตัวอักษรจะปรากฏในลำดับใด?await ทำอะไรเบื้องหลัง?