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

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

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

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

المنصة

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

الدعم

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

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

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

Event Loop في JavaScript: ليه setTimeout(0) مش بصفر فعلاً؟

📅 ٢٥ أبريل ٢٠٢٦⏱ 5 دقائق قراءة
Event Loop في JavaScript: ليه setTimeout(0) مش بصفر فعلاً؟

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

لو setTimeout(fn, 0) بيشتغل بعد Promise.resolve().then(fn) رغم إن الاتنين كأنهم بصفر مللي ثانية، المشكلة مش في المتصفح ولا في الجهاز. ده تصميم مقصود في كل JavaScript runtime، وفهمه بيوفر عليك ساعات debug على bug ترتيب تنفيذ خفي.

Event Loop في JavaScript: ليه setTimeout(0) مش بصفر فعلاً؟

شاشة محرر كود تعرض دوال JavaScript غير متزامنة مع setTimeout و Promise

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

JavaScript لغة single-threaded — يعني خيط تنفيذ واحد فقط. لكن متصفحك بيقدر يعمل HTTP request وعداد ثواني وفي نفس اللحظة يستجيب لكليكاتك. الآلية اللي بتخلي ده ممكن اسمها Event Loop. ولو ما فهمتهاش، هتكتب كود "بيشتغل" لكن بترتيب مش اللي توقعته، وهتلاقي bugs بتظهر في production بس.

تشبيه للمبتدئ: المطعم اللي فيه طبّاخ واحد

تخيّل مطعم فيه طبّاخ واحد بس — ده الـ Call Stack، الخيط الوحيد اللي بينفّذ كود فعلاً. النادل بيستلم الطلبات الجديدة ويحطها على لوحتين مختلفتين على الحيطة:

  • لوحة VIP: الطلبات اللي لازم تتعمل قبل أي حاجة تانية، حتى لو وصلت متأخر بعد طلبات عادية كتير.
  • لوحة عادية: الطلبات بترتيب وصولها، الواحد ورا التاني.

الطبّاخ كل ما يخلص طبق، بيبص على لوحة VIP الأول. لو فاضية، بياخد طلب واحد بس من اللوحة العادية، بيخلصه، ثم يرجع يبص على VIP تاني. ده بالظبط الفرق بين Microtask Queue (لوحة VIP) و Macrotask Queue (اللوحة العادية) جوّا محرك V8.

التعريف العلمي الدقيق

الـ Event Loop آلية تنسيق بتشتغل في كل JavaScript runtime — المتصفح، Node.js، Deno، Bun. الدورة بسيطة لكن صارمة، وموثّقة في HTML Living Standard:

  1. نفّذ كل اللي على الـ Call Stack حتى يفضى.
  2. نفّذ كل الـ microtasks المتراكمة (مش واحدة، كلها لحد ما الطابور يفضى).
  3. ارسم الـ UI لو في تغييرات (في المتصفح فقط).
  4. اسحب مهمة واحدة فقط من macrotask queue ونفّذها، ثم ارجع للخطوة 1.

الفرق المحوري: microtasks بتتنفّذ كلها قبل أي macrotask تانية. ده يعني Promise.then دائمًا قبل setTimeout، حتى لو الـ setTimeout اتسجّل قبله بمسافة في الكود.

ساعة بندول قديمة ترمز لتوقيت تنفيذ المهام في Event Loop وفرق المللي ثانية

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

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

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

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

queueMicrotask(() => console.log('4: queueMicrotask'));

console.log('5: sync');

الترتيب اللي هيطبع لو شغّلته في أي متصفح أو Node.js:

1: sync
5: sync
3: microtask
4: queueMicrotask
2: macrotask

السطر 5 طبع قبل 3 و4 لأن synchronous code أعلى أولوية من أي queue. وبعدين الـ microtasks (3 و4) كلها اتنفّذت قبل الـ macrotask (2)، مع إن setTimeout اتسجّل قبل الـ Promise في الكود. ده مش bug، ده تصميم.

التطبيق العملي: bug خفي شائع في async/await

تخيّل عندك دالة بتعمل update على state ثم بترسل request للسيرفر:

JavaScript
async function saveUser(user) {
  state.user = user;          // (A)
  await Promise.resolve();    // (B) — التحكم بيخرج هنا
  await fetch('/api/save', {
    method: 'POST',
    body: JSON.stringify(state.user)
  });
}

لو في كود تاني بيغيّر state.user بين النقطة A والنقطة B، الـ fetch هيبعت قيمة غلط. ليه؟ لأن await بيخرج التحكم للـ event loop، وأي microtask قبلك في الطابور بتتنفّذ قبل ما تكمل. الحل: خد snapshot قبل أي await:

JavaScript
async function saveUser(user) {
  const snapshot = { ...user };   // قفل القيمة قبل خروج التحكم
  await fetch('/api/save', {
    method: 'POST',
    body: JSON.stringify(snapshot)
  });
}

القاعدة العامة: لا تقرأ من state عام بعد await وأنت متوقّع إنه ما اتغيّرش. الـ event loop دار عجلة بين السطرين.

الفرق بين Microtask و Macrotask — جدول قرار

  • Microtasks: Promise.then/catch/finally، queueMicrotask، MutationObserver، نتائج await.
  • Macrotasks: setTimeout، setInterval، setImmediate (Node فقط)، عمليات I/O، أحداث DOM، رسائل postMessage.
دوائر إلكترونية متشابكة ترمز إلى طوابير Microtask و Macrotask داخل محرك V8

قياس فعلي: خطر الـ Microtask Starvation

شغّل الكود ده في تبويب متصفح وجرّب تضغط أي زرار في الصفحة:

JavaScript
function flood() {
  Promise.resolve().then(flood);
}
flood();

الواجهة بتتجمّد بالكامل. على Chrome 122 وجهاز M2: زمن الاستجابة لـ click ارتفع من 16ms لـ غير محدود. السبب إن المتصفح ما بيطلعش من microtask queue قبل ما تفضى، فما بيلحقش يرسم frame ولا يعالج input. بالمقابل، نفس الكود بـ setTimeout:

JavaScript
function flood() {
  setTimeout(flood, 0);
}
flood();

الواجهة بتفضل مستجيبة، CPU عالي لكن click latency حوالي 30ms، لأن كل tick في الـ event loop بياخد macrotask واحد فقط ثم يدّي فرصة للـ rendering و input handling.

متى لا تحتاج تفهم ده بعمق؟

لو بتكتب React أو Vue اعتيادي بدون scheduling مخصص، ما تحتاجش تحفظ الـ Event Loop عن ظهر قلب. الـ framework بيتعامل مع الموضوع. لكن تحتاج تفهمه فعلاً في 3 حالات:

  • تكتب library أو runtime (مش consumer).
  • تعمل debug لسلوك غير متوقع في ترتيب تنفيذ async code.
  • تعمل optimize للـ INP أو لـ long tasks بتقفّل الـ main thread.

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

تشغيل كل شغلك على microtasks (Promises) أسرع نظريًا، مفيش انتظار لـ frame. لكن المقابل: لو طوّلت في microtask queue، الواجهة بتتجمّد، والـ INP بيرتفع فوق 200ms — يعني Core Web Vitals بتاعتك بقت حمراء عند جوجل. الافتراض: عندك شغل ثقيل (parsing، عمليات حسابية على array كبير). لو محتاج تفصله، استخدم setTimeout(fn, 0) أو الأحدث scheduler.yield() بدل queueMicrotask. الأول بيدّي فرصة للرسم، التاني لا.

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

افتح DevTools في Performance tab، شغّل تطبيقك لمدة 10 ثواني وأنت بتتفاعل معاه، ودوّر على Long Tasks (المربعات الحمراء فوق 50ms). لو لقيت واحدة، بصّ هل فيها loop بيستخدم Promises أو await متتالية بدون break. لو أيوه، حوّل واحد من الـ awaits لـ await new Promise(r => setTimeout(r, 0)) في النص. هتلاحظ INP بينزل فورًا، وتقدر تقيس الفرق في نفس الـ Performance tab.

المصادر

  • HTML Living Standard — Event Loop: html.spec.whatwg.org
  • MDN — In depth: Microtasks and the JavaScript runtime: developer.mozilla.org
  • Jake Archibald — Tasks, microtasks, queues and schedules: jakearchibald.com
  • Node.js — The Event Loop, Timers, and process.nextTick: nodejs.org
  • web.dev — Optimize INP: web.dev
  • TC39 — Job Queues spec: tc39.es

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

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

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