مستوى القارئ: متوسط — المقال ده للمطورين اللي يعرفوا Promises و async/await بشكل عملي، وعندهم تساؤل ليه ترتيب التنفيذ في الكود الـ async مش بالمنطق المتوقع. لو لسه بتتعلم JavaScript من الصفر، ابدأ بـ Promises الأول.
لو سألك حد في interview: "ليه await Promise.resolve() بينفّذ قبل setTimeout(0) رغم إن الاتنين مفروض يأجّلوا التنفيذ؟" — معظم المطورين بيردوا غلط. السبب مش في JavaScript نفسه، السبب في إن فيه طابورين منفصلين جوّه الـ Event Loop، واحد بيتفضّى بالكامل قبل التاني. لو فهمت الفرق بينهم، هتعرف تتجنّب أبشع bug في تطبيقات الـ real-time: Microtask Starvation.
Event Loop: ليه ترتيب التنفيذ مش بديهي
المشكلة باختصار
الكود ده بيطبع نتائج بترتيب يخالف القراءة الطبيعية للسطور:
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");الترتيب الفعلي للمخرجات: 1, 4, 3, 2. لو توقعت 1, 4, 2, 3 لإن setTimeout اتكتب قبل Promise، الموضوع أعقد من ترتيب السطور. المتصفح و Node.js عندهم آلية تنفيذ مختلفة عن "first come, first served". وفهم الآلية دي بيفرق معاك لو بتشتغل على debugger معقّد أو بتظبّط performance.
مثال موظف المكتب للمبتدئ
تخيل موظف مكتب عنده تلات أنواع شغل:
- الورقة اللي قدامه على المكتب: لازم يخلّصها دلوقتي. ده الكود المتزامن.
- صندوق ملاحظات عاجلة جنبه: كل ما يخلّص ورقة من المكتب، يبصّ في الصندوق ويخلّص اللي فيه كله قبل ما يفتح ورقة جديدة. ده الـ Microtask Queue.
- صندوق طلبات في الممر: ميفتحوش إلا لما الصندوقين الأولين فاضيين خالص، ويبقى دوره ياخد طلب واحد بس ويرجع للمكتب. ده الـ Macrotask Queue.
الـ Promise.then = الملاحظات العاجلة جنبه. الـ setTimeout = طلبات الممر. الموظف ميقدرش يفتح طلب من الممر إلا لما الصندوق الجنبه يبقى فاضي. ده اللي بيخلّي الـ Promise يسبق setTimeout(0) حتى لو الـ timeout اتكتب الأول.
التعريف العلمي الدقيق
الـ Event Loop في JavaScript مكوّن من تلات أجزاء أساسية:
- Call Stack: مكان تنفيذ الكود المتزامن. كل function call بتتحط فوق بعض، وبتترفع لما تخلص.
- Microtask Queue: طابور لـ
Promise.then,queueMicrotask, . بيتفضّى بعد كل عملية في الـ Call Stack.