افهم كيف يجدول المحرك فعليا الكود المتزامن والمهام الدقيقة والمهام الكبرى.
افتح هذا الدرس في Kodokonتنفذ JavaScript الكود الخاص بك على خيط واحد، لكن حلقة الأحداث ليست معرفة في ECMAScript: بل مواصفة HTML (وlibuv من جهة Node.js) هي التي تصف كيفية عملها. يحتفظ المحرك بـ مكدس استدعاءات للكود الجاري تنفيذه حاليا، وطابور من المهام الكبرى (المؤقتات، الإدخال/الإخراج، الأحداث) وطابور من المهام الدقيقة (Promise.prototype.then، queueMicrotask، MutationObserver). القاعدة الذهبية: بعد كل مهمة كبرى، يفرغ طابور المهام الدقيقة بالكامل قبل إعادة التحكم.
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السكربت الأولي هو نفسه مهمة كبرى: حتى ينتهي، لا يعمل أي شيء آخر. حالما يصبح مكدس الاستدعاءات فارغا، يفرغ المحرك طابور المهام الدقيقة، بما في ذلك المهام الدقيقة المضافة أثناء التفريغ. تفسر هذه التفصيلة لماذا يعمل استدعاءان متسلسلان لـ then قبل setTimeout(fn, 0): كل then يجدول مهمة دقيقة أخرى، ولا يعاد التحكم أبدا حتى يفرغ الطابور.
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 على هذه الآلية: فهي تعلق الدالة غير المتزامنة، وتعيد التحكم إلى المستدعي، ثم تجدول الاستئناف كمهمة دقيقة. حتى await null يطلق هذا التعليق، لأن القيمة تغلف في وعد محلول بالفعل. لهذا السبب لا يعمل الكود الموضوع بعد 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 فعليا في الخفاء؟