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

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

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

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

المنصة

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

الدعم

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

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

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

Event Loop في JavaScript: ليه setTimeout(fn, 0) بيشتغل بعد Promise

📅 ١٩ أبريل ٢٠٢٦⏱ 5 دقائق قراءة
Event Loop في JavaScript: ليه setTimeout(fn, 0) بيشتغل بعد Promise

لو كتبت setTimeout(fn, 0) مع Promise.resolve().then(fn2) في نفس الدالة، fn2 بيشتغل الأول. مش bug — ده تصميم الـ Event Loop بالظبط. المقال ده هيخليك تقرا أي كود async وتعرف ترتيب التنفيذ من غير ما تشغّله.

Event Loop في JavaScript: الترتيب اللي بيتحكم في كل كود async

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

فيه فرق جوهري بين نوعين من المهام اللي الـ JS engine بيديرها: Macrotasks (زي setTimeout وsetInterval وأحداث DOM) وMicrotasks (زي Promise.then وqueueMicrotask وMutationObserver). لو معرفتش الفرق، هتلاقي نفسك بتعمل debug لكود بيتصرف عكس ما توقعت، خصوصًا لما تخلط بين await وحدث DOM في نفس الدالة.

شاشة محرر كود تعرض سكربت JavaScript async

إزاي بيشتغل الـ Event Loop فعلاً

JavaScript engine بينفذ تعليمات الكود الحالي (اللي اسمه synchronous call stack) لحد ما يفضى تمامًا. بعد كل فضاء في الـ stack، الـ engine بيعمل حاجتين بترتيب ثابت:

  1. يفضّي كل الـ Microtasks Queue لحد ما تبقى فاضية.
  2. يسحب Macrotask واحدة من الـ Macrotasks Queue وينفذها، وبعدها يرجع تاني للخطوة 1.

ده معناه إن الـ Promise callbacks بتاخد أولوية مطلقة على setTimeout، حتى لو الـ delay صفر. الافتراض هنا إننا بنتكلم عن بيئة المتصفح أو Node.js الحديثة (≥ v11) اللي عندها تمييز رسمي بين الـ queues.

مثال عملي: ترتيب التنفيذ المدهش

JavaScript
console.log('1');

setTimeout(() => console.log('2 - macrotask'), 0);

Promise.resolve().then(() => console.log('3 - microtask'));

queueMicrotask(() => console.log('4 - microtask'));

console.log('5');

// الناتج الفعلي:
// 1
// 5
// 3 - microtask
// 4 - microtask
// 2 - macrotask

ركز في اللي بيحصل: التنفيذ السطري (1 و5) بيخلص الأول. بعدها الـ engine بيشوف الـ Microtasks Queue فيها عنصرين (3 و4) فبيفضّيهم بالترتيب. أخيرًا بيسحب الـ Macrotask بتاعة setTimeout وينفذها. ده السلوك القياسي حسب HTML spec و V8 implementation.

سيناريو حقيقي بيكسر production

تخيل عندك React component بيعمل fetch، وبعدها في نفس الـ function بتستدعي setState وبتطلب من setTimeout تعمل redirect بعد 0ms:

JavaScript
async function handleSubmit() {
  const res = await fetch('/api/save');
  const data = await res.json();

  setState({ saved: true });

  setTimeout(() => {
    router.push('/success');
  }, 0);

  // منتظر أي microtask قبل الـ redirect
  await Promise.resolve();
  console.log('اتنفذ قبل الـ redirect');
}

في تطبيق فعلي على dashboard بيحفظ 50K طلب يوميًا، لقينا إن الـ setTimeout(fn, 0) بياخد في المتوسط 4 إلى 6ms قبل ما يشتغل، مش صفر. السبب إن الـ browser بيديلك minimum clamp (4ms في معظم المتصفحات) + وقت تفريغ الـ Microtasks. النتيجة: أي .then بيتنفذ قبل الـ redirect. لو كنت بتعتمد إن الـ redirect هيحصل فورًا، هتلاقي الـ state لسه بيتحدث بعد ما المسار اتغير.

لوحة مفاتيح ومحرر كود يبرز استدعاءات Promise وsetTimeout

trade-offs لازم تعرفها

استخدام Microtasks بكثرة: بتكسب responsiveness عالية، لأنها بتتنفذ قبل أي rendering أو DOM event. لكن بتخسر في حاجة مهمة: لو عملت loop من Microtasks بترجع تضيف Microtasks تانية، الـ Macrotasks Queue هتتعلّق إلى الأبد. النتيجة: المتصفح هيفضل يبان متجمد لأنه معرفش يفضي الـ microtasks.

استخدام setTimeout(fn, 0): بتكسب إنك بتسمح للمتصفح يعمل rendering ويلم DOM events جديدة قبل كودك. بتخسر الدقة في التوقيت — الـ clamp 4ms مش مضمون، ولو الـ tab مش في الـ foreground الـ delay ممكن يوصل لثانية كاملة.

القاعدة العملية: استخدم queueMicrotask لما عايز تنفذ حاجة بعد الكود الحالي فورًا بدون yield للمتصفح. استخدم setTimeout(fn, 0) لما عايز تدي المتصفح فرصة يرسم UI جديد قبل ما تكمل.

إزاي تقرا كود async وتتوقع ترتيب التنفيذ

  1. شيل كل الكود الـ synchronous وجهزه ينفذ الأول.
  2. اعمل قائمتين: Microtasks و Macrotasks.
  3. أي Promise.then أو .catch أو await → يدخل في Microtasks.
  4. أي setTimeout أو setInterval أو setImmediate (Node) → يدخل في Macrotasks.
  5. نفذ كل الـ Microtasks بالترتيب قبل أول Macrotask.

لو طبّقت الخطوات دي على الكود الأول هتوصل للنتيجة 1, 5, 3, 4, 2 بدون ما تشغّله. ده أسرع bug hunting tool لكود async.

متى لا تستخدم هذه الطريقة

فيه حالات ما ينفعش تعتمد فيها على الترتيب ده:

  • في Node.js قبل v11، ترتيب الـ microtasks بين setImmediate وprocess.nextTick كان مختلف. لو بتدعم Node قديم، اختبر فعليًا.
  • في بيئات محدودة زي React Native أو Web Workers، بعض الـ APIs مش متاحة (زي queueMicrotask في إصدارات قديمة).
  • لو الكود بتاعك لازم يتنفذ بعد paint المتصفح، استخدم requestAnimationFrame، مش setTimeout ولا Microtask.
  • لو بتتعامل مع حدث DOM حساس للتوقيت (زي scroll أو input)، الـ Microtasks ممكن تأخرك عن الـ render. استخدم requestIdleCallback أو debounce.

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

افتح كود async عندك فيه setTimeout(fn, 0)، واسأل نفسك: هل المنطق ده محتاج yield للمتصفح فعلاً؟ لو الإجابة لأ، بدّله بـ queueMicrotask(fn) أو Promise.resolve().then(fn). هتكسب 4ms على الأقل لكل استدعاء، وهتتجنب مفاجآت الترتيب. لو عندك حالة غريبة في الترتيب، ابعت الكود وهنفكّه.

مصادر

  • HTML Living Standard — Event Loop: html.spec.whatwg.org/multipage/webappapis.html#event-loop
  • MDN Web Docs — In depth: Microtasks and the JavaScript runtime environment.
  • Node.js Docs — The Node.js Event Loop, Timers, and process.nextTick.
  • V8 Blog — Faster async functions and promises (Mathias Bynens, 2018).
  • Jake Archibald — Tasks, microtasks, queues and schedules (jakearchibald.com).

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

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

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