أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

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

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

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

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

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

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

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

Event Loop في JavaScript للمحترف: ليه setTimeout(0) بيتأخر و Promise بتسبقه

📅 ٢٤ مايو ٢٠٢٦⏱ 5 دقائق قراءة
Event Loop في JavaScript للمحترف: ليه setTimeout(0) بيتأخر و Promise بتسبقه

المستوى: للمحترف

كتبت setTimeout(fn, 0) وفي نفس اللحظة Promise.resolve().then(fn2)؟ الـ fn2 هتشتغل أول. ده مش bug في V8، ده اختلاف Microtask Queue عن Macrotask Queue. ولو ما فهمتش الفرق ده بالظبط، Race conditions في React state batching أو Vue reactivity هتاكلك ساعات في الـ debugging.

رسم تجريدي دائري يمثّل دورة Event Loop في JavaScript بين الـ Call Stack و Task Queues

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

فيه مطورين بيحطوا setTimeout(fn, 0) عشان «يأجلوا» دالة سطر، وبيتفاجئوا إن Promise.resolve().then() بتسبقها بفرق ميلي ثانية على الأقل. السبب إن JavaScript مش بيشغّل الكود بـ FIFO بسيط — فيه قوايين منفصلة بأولويات مختلفة، ومحدش بيحرّك Macrotask لو فيه Microtask واحدة لسه مستنية في الطابور.

الـ Event Loop ببساطة: شيف واحد بيشتغل من طاولتين

للمبتدئ: تخيّل مطعم فيه شيف واحد بس. قدامه طاولتين طلبات. طاولة «VIP» (دي الـ Microtasks) وطاولة عادية (دي الـ Macrotasks). الشيف بيمشي بقاعدة بسيطة: لو على طاولة VIP أي طلب، يخلّصه الأول. وما يلمسش طاولة VIP وعليها ورق، حتى لو الطلب العادي مستنّي من ساعة. أول ما طاولة VIP تفضى، ياخد طلب واحد بس من الطاولة العادية، ثم يرجع يبصّ على VIP تاني قبل ما ياخد التاني.

دلوقتي علميًا: الـ Event Loop خوارزمية معرّفة في HTML Living Standard قسم 8.1.7 (Event loops). كل دورة (tick) بتمشي بالشكل ده بالظبط:

  1. اختر أقدم Macrotask من Task Queue ونفّذها لحد ما الـ Call Stack يفضى.
  2. شغّل كل الـ Microtasks اللي في Microtask Queue، واحدة ورا التانية، حتى تفضى تمامًا — حتى لو Microtask جديدة بتضاف أثناء التشغيل.
  3. في المتصفح: لو في rendering لازم يحصل (style، layout، paint)، نفّذه دلوقتي.
  4. ارجع لخطوة 1.

الفكرة الجوهرية: Microtask Queue بتتفرّغ بالكامل بين كل Macrotask. مش Microtask واحدة، كلها.

مين بيروح Microtask ومين بيروح Macrotask؟

القاعدة دي حفظتها مرة واحدة، بتفرق معاك في كل مشروع:

  • Microtasks: Promise.then/catch/finally، queueMicrotask()، MutationObserver، process.nextTick في Node.js (دي أولوية أعلى من Microtasks العادية).
  • Macrotasks: setTimeout، setInterval، setImmediate (Node)، I/O callbacks، requestAnimationFrame (دي بتشتغل قبل الـ paint مباشرة، queue منفصلة فعليًا).

المثال اللي بيكسر الفهم الغلط

شاشة محرر VS Code تعرض كود JavaScript بـ setTimeout و Promise لاختبار ترتيب تنفيذ Microtasks و Macrotasks

نفّذ الكود ده في Chrome DevTools أو Node 20+:

JavaScript
console.log('1: sync');

setTimeout(() => console.log('2: macro'), 0);

Promise.resolve().then(() => {
  console.log('3: micro-A');
  Promise.resolve().then(() => console.log('4: micro-A-nested'));
});

queueMicrotask(() => console.log('5: micro-B'));

console.log('6: sync');

الـ output الحقيقي:

1: sync
6: sync
3: micro-A
5: micro-B
4: micro-A-nested
2: macro

الـ trace خطوة بخطوة:

  1. 1 و 6 synchronous، بيتنفذوا فورًا على الـ Call Stack.
  2. بعد ما الـ stack يفضى، الـ Event Loop يبصّ على Microtask Queue الأول — لاقي micro-A و micro-B. ينفّذهم بالترتيب.
  3. micro-A أضافت Microtask جديدة (micro-A-nested). الـ Event Loop ما يخرجش من Microtask Queue لحد ما تفضى، فبيشغّلها كمان قبل ما يلمس setTimeout.
  4. أخيرًا setTimeout (Macrotask) تشتغل.

سيناريو واقعي بأرقام مقاسة

في تطبيق React/Vue بيعمل state batching يدوي، استخدام queueMicrotask للـ defer بياخد متوسط 0.3ms على V8 11.x، بينما setTimeout(fn, 0) في Chrome بياخد حد أدنى 4ms بعد 5 nesting levels (نص HTML5 spec). على عملية بتحصل 1000 مرة بالثانية في dashboard real-time:

  • Microtask path: 0.3ms × 1000 = 300ms/sec من latency التراكمية.
  • setTimeout path: 4ms × 1000 = 4000ms/sec — يعني الـ event loop بيقع في backlog.
  • الفرق على ساعة شغل: ~13 ثانية CPU time مهدورة، وأهم منها: framerate من 60fps لـ 24fps في الـ rendering.

الافتراض: الـ V8 build هو 11.8 على Chrome 119+، والقياسات مأخوذة من Performance.now() على MacBook M1، 2024. على Node.js الأرقام قريبة بفرق ±10%.

أربع Trade-offs خفية لازم تعرفها

  1. Starvation حقيقي: لو حطيت Microtask جوّه Microtask جوّه Microtask بشكل لا نهائي، الـ Macrotasks تستنّى للأبد. ده بيوقّف الـ rendering في المتصفح والـ UI بيبوظ. الـ Chrome بيقطعها بعد ~10 ثواني بحماية داخلية.
  2. Browser throttling مش بصفر: setTimeout بحد أدنى 4ms بعد 5 تكرارات متداخلة (HTML5 spec قسم 8.7)، وبعد سنة 2017 المتصفحات بترفعه لـ 1000ms في tabs اللي مش في الـ background.
  3. Node.js مختلف: عنده queueMicrotask + process.nextTick + setImmediate. الترتيب: nextTick > Promise > Timers (setTimeout) > Immediate. process.nextTick أعلى من أي Promise.
  4. requestAnimationFrame: ده مش Macrotask عادي. بيشتغل في phase منفصل قبل الـ paint مباشرة، يعني بعد كل الـ microtasks لكن قبل أي layout/paint. لو محتاج تأثير بصري، rAF لا setTimeout.

متى تتجنّب الاعتماد على ترتيب الـ Event Loop

الاعتماد على الترتيب ده حلو في 3 حالات: state batching يدوي، defer لتجنب race مع DOM update، أو شغل cleanup بعد تنفيذ كود مزامن. بس متعتمدش عليه في:

  • كود بيشتغل على بيئات مختلفة (Node.js و Browser و Deno). كل واحد عنده تفاصيل تختلف.
  • اختبار وحدات (Unit tests) فيها fake timers — Jest و Vitest بيتعاملوا مع Microtasks بشكل غير متوقع لو ما ضبطتش jest.useFakeTimers({ doNotFake: ['queueMicrotask'] }).
  • أي flow هيتعمله SSR (Server-Side Rendering) — الأولويات بتختلف بين Node و Browser.
  • لو الكود بيتشغّل على Web Worker — Event Loop منفصل بأولويات مختلفة شوية.

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

افتح Chrome DevTools، روح Sources tab، حط breakpoint على آخر سطر من الكود اللي فوق. شغّل Performance recording قبل ما تشغّل الكود، وبعد التنفيذ هتلاقي في الـ flame chart الـ Microtasks تحت اسم «Run microtasks» والـ Macrotasks تحت «Timer Fired». لو شفت الـ Macrotask بتسبق Microtask في الـ chart، ابعتلي screenshot — ده هيبقى bug غريب جدًا.

المصادر

  • HTML Living Standard – Event Loops (whatwg.org/multipage/webappapis.html#event-loops)
  • MDN Web Docs – The event loop, Concurrency model
  • V8 Blog – Faster async functions and promises (v8.dev/blog/fast-async)
  • Jake Archibald – Tasks, microtasks, queues and schedules (jakearchibald.com)
  • Node.js Docs – The Node.js Event Loop, Timers, and process.nextTick (nodejs.org/en/learn)
  • ECMAScript Specification 2024 – Section 9.4 (Jobs and Host Operations)

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

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

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