أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالمناهج والباقات
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • المناهج والباقات
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالمناهجالمدونةالدخول
البرمجة بالعربي

الـ Event Loop في JavaScript للمتوسط: ليه setTimeout بصفر بيتأخّر عن Promise؟

متوسط١٥ يونيو ٢٠٢٦5 دقائق قراءة
الـ Event Loop في JavaScript للمتوسط: ليه setTimeout بصفر بيتأخّر عن Promise؟

مستوى المقال: متوسط — محتاج تكون كتبت JavaScript قبل كده وتعرف setTimeout و Promise شكلهم إيه، بس مش لازم تكون فاهم الـ Event Loop. هنبدأ بمثال بسيط جدًا للمبتدئ ثم نرجع للتفصيل العلمي.

الـ Event Loop في JavaScript: ليه setTimeout بصفر بيتأخّر عن Promise؟

لو كتبت setTimeout(fn, 0) وفاكر إنه هيشتغل قبل أي حاجة، الكود ده هيطلّع لك ترتيب مقلوب عن توقعك. بعد المقال ده هتعرف بالظبط ليه الـ Promise.then بيسبق الـ setTimeout حتى لو الاتنين "جاهزين"، وهتقدر تتنبأ بترتيب تنفيذ أي كود async من غير ما تشغّله.

المشكلة باختصار

JavaScript بتشتغل على thread واحد فقط. يعني سطر واحد بيتنفّذ في اللحظة. لكن المتصفح والـ Node بيخلّوك تكتب كود "متزامن المظهر" يستنى عمليات بطيئة (طلب شبكة، تايمر، قراءة ملف) من غير ما يجمّد الصفحة. اللي بيدير ده كله اسمه الـ Event Loop. سوء فهم ترتيبه بيسبّب bugs غريبة: قيمة بتتحدّث في الوقت الغلط، أو UI بيتجمّد بدون سبب واضح.

كود JavaScript معروض على شاشة يوضّح تنفيذ setTimeout و Promise والترتيب الحقيقي للـ Event Loop

المفهوم بمثال بسيط: الكاشير والطابورين

تخيّل كاشير واحد في سوبر ماركت. الكاشير ده هو الـ Call Stack: بيعمل حاجة واحدة بس في اللحظة. قدامه طابورين:

  • طابور العملاء العاديين = الـ Task Queue (المهام الكبيرة / macrotasks). ده مكان ال_setTimeout: setTimeout.
  • طابور VIP = الـ Microtask Queue (المهام الصغيرة). ده مكان الـ Promise.then.

القاعدة عند الكاشير صارمة: قبل ما يستدعي أي عميل عادي جديد، لازم يخلّص كل الـ VIP اللي مستنيين الأول. حتى لو دخل VIP جديد وهو بيخدم VIP، بيخدمه قبل ما يلتفت للطابور العادي. ده بالظبط ليه الـ Promise بيسبق الـ setTimeout.

الشرح العلمي: ثلاث قطع بتتحرك

دلوقتي نرجع للتعريف الدقيق. في كل دورة (tick) الـ Event Loop بيشتغل بالترتيب ده:

  1. Call Stack: ينفّذ كل الكود المتزامن (synchronous) لحد ما الستاك يفضى.
  2. Microtask Queue: يفرّغ كل الـ microtasks بالكامل (Promise.then، queueMicrotask، await اللي بعد resolve). ولو microtask ولّدت microtask تانية، دي كمان بتتنفّذ في نفس الدورة.
  3. Render: في المتصفح، هنا بس ممكن يحصل إعادة رسم للشاشة.
  4. Task Queue: ياخد مهمة واحدة بس (زي callback بتاع setTimeout) ثم يرجع من أول الخطوة 2.

النقطة المفصلية: الـ microtasks بتتفرّغ بالكامل بين كل macrotask والتانية. المتصفح مش بيعمل render أصلًا وسط الـ microtasks. (المصدر: مواصفة WHATWG HTML، قسم event loops، وتوثيق MDN).

الكود اللي بيثبت الكلام

الكود ده شغّال زي ما هو على Node 22 وفي console المتصفح:

JavaScript
console.log('1: كود متزامن');

setTimeout(() => console.log('2: setTimeout بصفر'), 0);

Promise.resolve().then(() => console.log('3: microtask من Promise'));

console.log('4: كود متزامن تاني');

// الناتج الفعلي:
// 1: كود متزامن
// 4: كود متزامن تاني
// 3: microtask من Promise
// 2: setTimeout بصفر

ركّز على إن 3 طلع قبل 2 رغم إن الاتنين كانوا "جاهزين" في نفس اللحظة. السبب إن الـ Promise راح لطابور الـ VIP اللي بيتفرّغ بالكامل قبل أول عميل عادي.

شاشة برمجة تعرض كود يمثل طابور المهام Task Queue وطابور المهام الصغيرة Microtask Queue في محرك JavaScript

رقم لازم تعرفه: الـ setTimeout مش بصفر فعلًا

لو حطيت setTimeout(fn, 0)، المواصفة بتفرض حد أدنى للتأخير. بعد 5 استدعاءات متداخلة (nested) بيتم تثبيت الحد الأدنى على 4ms في المتصفحات. يعني loop فيه آلاف الـ setTimeout(0) المتداخلة هتلاقيه أبطأ بكتير من توقعك. لو محتاج تأجيل لأقرب فرصة من غير الـ 4ms دي، استخدم queueMicrotask() أو Promise.resolve().then() لأنها microtask مش macrotask.

الـ trade-off: الـ microtask ممكن تجوّع الـ Event Loop

بما إن الـ microtask queue بتتفرّغ بالكامل قبل أي macrotask أو render، لو عملت سلسلة microtasks لا نهائية، المتصفح هيتجمّد تمامًا — مفيش رسم، ومفيش حتى استجابة للنقر. مثال خطر:

JavaScript
function loop() {
  Promise.resolve().then(loop); // microtask بتولّد microtask للأبد
}
loop(); // الصفحة هتتجمّد، الـ setTimeout اللي تحت ده عمره ما هيشتغل
setTimeout(() => console.log('أنا مش هطلع أبدًا'), 0);

المكسب من الـ microtasks: إنها بتضمن إن منطقك بيخلّص قبل أي رسم، فبتمنع حالات نص-محدّثة. الخسارة: لو أسأت استخدامها بتقفل الـ thread بالكامل. القاعدة العملية: استخدم microtasks للحاجات القصيرة الحاسمة، والشغل الطويل قسّمه على macrotasks (setTimeout أو scheduler.postTask) علشان تدّي الفرصة للرسم وللأحداث.

متى متشغلش بالك بالموضوع ده

لو كل كودك متزامن (synchronous) من غير Promise ولا تايمرات ولا async/await، الترتيب ده مش هيأثّر عليك أصلًا — الكود بيمشي من فوق لتحت وخلاص. كمان في معظم كود التطبيقات العادي، انت بتستخدم await وكفاية، ومش محتاج تحفظ ترتيب الطابورين. الموضوع بيبقى مهم فعلًا في حالتين: لما تكتب library فيها logic توقيت دقيق، أو لما تطارد bug سببه إن حاجة اشتغلت قبل/بعد حاجة تانية بعكس توقعك.

المصادر

  • MDN — In depth: Microtasks and the JavaScript runtime environment: رابط
  • WHATWG HTML Living Standard — قسم Event loops (الترتيب الرسمي للـ tasks و microtasks): رابط
  • javascript.info — Event loop: microtasks and macrotasks: رابط
  • MDN — setTimeout minimum delay clamp (4ms بعد التداخل): رابط

الخطوة التالية

افتح console في المتصفح دلوقتي والصق كود الـ 1,2,3,4 اللي فوق. بعدها جرّب تضيف queueMicrotask(() => console.log('5')) بعد الـ Promise مباشرة، واتوقّع الترتيب قبل ما تضغط Enter. لو طلع غير توقعك، رجّع لقاعدة الكاشير: العادي الأول، بس الـ VIP بيتفرّغ بالكامل قبله.

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة