هذا المقال يتطلب مستوى: متوسط.
لو كتبت setTimeout(fn, 0) وانت فاكر إنها هتشتغل حالًا، الكود بتاعك فيه bug صامت مستنّي وقته. بعد المقال ده هتعرف بالظبط ترتيب تنفيذ أي كود غير متزامن في JavaScript، وهتبطّل تتفاجأ بالنتيجة.
Event Loop في JavaScript: ليه setTimeout(0) مش بيتنفّذ فورًا
المشكلة باختصار
JavaScript بتشتغل على thread واحد. يعني سطر واحد بس بيتنفّذ في اللحظة الواحدة. ومع ذلك التطبيق بيتعامل مع شبكة وتايمرات وأحداث UI في نفس الوقت من غير ما يتجمّد. الحاجة اللي بتنظّم ده اسمها Event Loop.
المشكلة إن معظم المطورين بيتعاملوا معاه كصندوق أسود. فبيتفاجئوا لما Promise.then يتنفّذ قبل setTimeout(fn, 0) رغم إن الاتنين "مؤجَّلين". وده مش تفصيلة أكاديمية: ترتيب التنفيذ ده بيحدّد هل واجهتك هتستجيب للمستخدم في 16 مللي ثانية ولا هتتجمّد ثانية كاملة.
مثال يقرّب الفكرة قبل التعريف العلمي
تخيّل مكتب فيه موظف استقبال واحد. المكتب اللي قدّامه هو المساحة اللي بيشتغل فيها دلوقتي. وعنده درجين للمهام المؤجَّلة:
- درج أصفر: المهام المستعجلة جدًا (الـ Promises).
- درج أزرق: المهام العادية المؤجَّلة (الـ
setTimeoutوأحداث الشبكة).
القاعدة عند الموظف بسيطة وصارمة: كل ما يخلّص المهمة اللي في إيده، يفضّي الدرج الأصفر بالكامل الأول. مش مهمة واحدة منه — كله. وبعد ما الأصفر يفضى تمامًا، ياخد ورقة واحدة بس من الدرج الأزرق، يشتغل فيها، وبعدين يرجع يبصّ على الأصفر تاني.
عشان كده setTimeout(fn, 0) بيتأخّر: الورقة بتاعته راحت الدرج الأزرق، وأي Promise موجود في الأصفر هياخد دوره قبلها — مهما كان عددهم.
التعريف العلمي الدقيق
دلوقتي نشيل المثال ونحط المصطلحات الحقيقية. الـ Event Loop بيشتغل على أربع مكوّنات:
- Call Stack: المكان اللي بيتنفّذ فيه الكود المتزامن دلوقتي. لو فيه أي حاجة هنا، مفيش حاجة تانية بتتحرّك.
- Web APIs / Node APIs: التايمرات وطلبات الشبكة وقراءة الملفات بتتسلّم لبيئة التشغيل (المتصفح أو Node)، مش لـ JavaScript نفسها.
- Macrotask Queue (طابور المهام): بيستقبل callbacks الـ
setTimeoutوsetIntervalوأحداث الـ I/O. - Microtask Queue (طابور المهام الدقيقة): بيستقبل callbacks الـ Promises و
queueMicrotaskوMutationObserver.
خوارزمية الـ Event Loop كالتالي: نفّذ كل الكود المتزامن في الـ Call Stack لحد ما يفضى. بعد كل مهمة (task) واحدة، صرّف الـ Microtask Queue بالكامل قبل أي حاجة تانية. بعدين اسمح للمتصفح يعمل render لو محتاج. وبعد كده بس خُد مهمة واحدة من الـ Macrotask Queue. كرّر.