مستوى المقال: متوسط. بيفترض إنك بتكتب دوال جافاسكريبت وبتستخدم setTimeout وPromise، بس محدش شرحلك ليه ترتيب تنفيذهم بيطلع عكس اللي كتبته.
هتخرج من هنا عارف بالظبط ليه Promise.then بيشتغل قبل setTimeout(fn, 0)، وليه سطر واحد ثقيل بيقدر يجمّد سيرفر Node بالكامل، وإزاي تتجنب ده. الشرح كله مبني على فرضية إنك بتشتغل على Node.js أو متصفح حديث بمحرك V8.
الـ Event Loop في جافاسكريبت: القلب اللي بيدير كل شيء غير متزامن
جافاسكريبت لغة بخيط واحد. يعني بتنفّذ أمر واحد في اللحظة الواحدة. ومع كده بتقدر تخدم آلاف الاتصالات في نفس الوقت من غير ما تتجمّد. الحاجة اللي بتخلّي ده ممكن اسمها الـ Event Loop.
مثال قبل التشريح: الطباخ الواحد في المطبخ
تخيّل مطعم فيه طباخ واحد بس. الطلبات بتيجي على ورقة. الطباخ بيمسك طلب، يشتغل فيه لحد ما يخلص، وبعدين يمسك اللي بعده. ده الخيط الواحد.
دلوقتي طلب فيه طبق محتاج يقعد 10 دقايق في الفرن. هل الطباخ هيقف قدام الفرن 10 دقايق يتفرّج؟ طبعًا لأ. هيحط الطبق في الفرن، يظبّت مؤقّت، ويرجع يمسك الطلب اللي بعده. أول ما المؤقّت يرنّ، بيحط الورقة دي في طابور "جاهز للاستكمال". الطباخ بيخلّص اللي في إيده الأول، وبعدين بيبص للطابور.
في المطبخ ده فيه نوعين ملاحظات. ملاحظة عادية بتتحط في آخر الطابور الكبير (زي رنّة الفرن). وملاحظة عاجلة من نفس الزبون اللي بيكلّمه دلوقتي، دي لازم تتعمل قبل ما يمسك أي طاولة جديدة. النوع العاجل ده هو الـ microtask. والنوع العادي هو الـ macrotask.
التشريح العلمي: مكدّس نداءات وطابورين وحلقة بتفحص باستمرار
خلّينا نحطّ الأسماء الحقيقية مكان المثال:
- Call Stack (مكدّس النداءات): مكان تنفيذ الكود المتزامن. أي دالة بتشتغل بتتحط فوقه، وبتتشال أول ما ترجع.
- Web APIs / libuv: الجهة اللي بتتولى المؤقّتات و I/O والشبكة بره الخيط الرئيسي. دي الفرن في المثال.
- Macrotask Queue (طابور المهام الكبيرة): بيستقبل ردود
setTimeoutوsetIntervalو I/O callbacks. - Microtask Queue (طابور المهام الدقيقة): بيستقبل ردود
Promise.thenوqueueMicrotaskوasync/await.
الـ Event Loop نفسه قاعدة بسيطة بتتكرّر: نفّذ كل الكود المتزامن لحد ما الـ Call Stack يفضى، بعدها فرّغ طابور المهام الدقيقة بالكامل، وبعد كده خُد مهمة واحدة بس من طابور المهام الكبيرة، وكرّر.
القاعدة اللي بتفسّر كل حاجة: بعد كل مهمة كبيرة، الـ Event Loop بيفرّغ كل المهام الدقيقة الموجودة قبل ما يلمس المهمة الكبيرة اللي بعدها.
ليه Promise بيسبق setTimeout(0)؟
جرّب الكود ده بنفسك في المتصفح أو Node: