Kodokon kodokon.com

حلقة الأحداث: مكدس الاستدعاءات، المهام الدقيقة، المهام الكبرى

افهم كيف يجدول المحرك فعليا الكود المتزامن والمهام الدقيقة والمهام الكبرى.

10 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

تنفذ JavaScript الكود الخاص بك على خيط واحد، لكن حلقة الأحداث ليست معرفة في ECMAScript: بل مواصفة HTML (وlibuv من جهة Node.js) هي التي تصف كيفية عملها. يحتفظ المحرك بـ مكدس استدعاءات للكود الجاري تنفيذه حاليا، وطابور من المهام الكبرى (المؤقتات، الإدخال/الإخراج، الأحداث) وطابور من المهام الدقيقة (Promise.prototype.then، queueMicrotask، MutationObserver). القاعدة الذهبية: بعد كل مهمة كبرى، يفرغ طابور المهام الدقيقة بالكامل قبل إعادة التحكم.

JAVASCRIPT
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 يجدول مهمة دقيقة أخرى، ولا يعاد التحكم أبدا حتى يفرغ الطابور.

JAVASCRIPT
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 أبدا بشكل متزامن.

JAVASCRIPT
async function main() {
  console.log("before await");
  await null;
  console.log("after await");
}

main();
console.log("sync code");
// before await, sync code, after await
await يعلق الدالة ويعيد جدولة الباقي.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. سكربت يسجل C بشكل متزامن، ويجدول A عبر setTimeout(fn, 0) وB عبر Promise.resolve().then. بأي ترتيب تظهر الأحرف؟
    • A, B, C
    • C, A, B
    • C, B, A
    • B, C, A
  2. متى يفرغ طابور المهام الدقيقة؟
    • بعد كل مهمة كبرى، بالكامل، بما في ذلك المهام الدقيقة المضافة أثناء التفريغ
    • تعمل مهمة دقيقة واحدة فقط بين مهمتين كبريين
    • كل 16 مللي ثانية، قبل رسم الصفحة مباشرة
    • مرة واحدة فقط بعد معالجة جميع المهام الكبرى
  3. ماذا يفعل await فعليا في الخفاء؟
    • يحجب الخيط حتى يحل الوعد
    • يعلق الدالة غير المتزامنة ويجدول استئنافها كمهمة دقيقة
    • يحول باقي الدالة إلى مهمة كبرى
    • لا يفعل شيئا إذا لم تكن القيمة المنتظرة وعدا