| المفهوم | التعريف |
|---|---|
| Call Stack | مكان تنفيذ الكود المتزامن سطراً بسطر |
| Task Queue | طابور مهام setTimeout والأحداث العادية |
| Microtask Queue | طابور Promises، أولوية أعلى من Task Queue |
| Event Loop | الآلية التي تُنقل المهام من الطوابير للمكدس عند فراغه |
جافاسكريبت لغة أحادية الخيط (Single-Threaded)، أي أنها تُنفذ سطراً واحداً من الكود في وقت واحد فقط، ولا يمكنها تنفيذ سطرين في نفس اللحظة بالضبط. لكن هذا لا يعني أنها بطيئة أو معطّلة عند انتظار عمليات تستغرق وقتاً (كجلب بيانات من خادم أو انتظار مؤقت) — وهنا يأتي دور البرمجة غير المتزامنة (Asynchronous Programming).
المحرك الذي يجعل هذا ممكناً يُسمى Event Loop (طابور الأحداث)، وهو آلية تعمل في الخلفية باستمرار تراقب "مكدس الاستدعاء" (Call Stack) — وهو المكان الذي تُنفَّذ فيه الأكواد المتزامنة سطراً بسطر — و"طابور المهام" (Task Queue أو Callback Queue) الذي يحتوي الدوال الجاهزة للتنفيذ بعد انتهاء عملية غير متزامنة (مثل انتهاء مؤقت أو وصول استجابة من الخادم).
قاعدة Event Loop الذهبية: لا تُنقل أي مهمة من طابور المهام إلى مكدس الاستدعاء للتنفيذ إلا إذا كان المكدس فارغاً تماماً — أي أن كل الكود المتزامن (Synchronous) في البرنامج ينتهي أولاً قبل تنفيذ أي كود غير متزامن مُعلَّق، حتى لو كان ذلك الكود جاهزاً للتنفيذ منذ فترة. هذا يُفسر لماذا setTimeout(fn, 0) لا يُنفذ فوراً رغم أن التأخير صفر مللي ثانية — يجب أن ينتظر حتى ينتهي كل الكود المتزامن الحالي أولاً.
توجد أيضاً طوابير أعلى أولوية تُسمى "Microtask Queue" (مثل Promises)، والتي تُعالَج بالكامل قبل أي مهمة في طابور المهام العادي (مثل setTimeout)، حتى لو كانت جاهزة قبلها زمنياً. فهم هذا الترتيب (Call Stack أولاً، ثم Microtasks، ثم Task Queue العادي) ضروري جداً لتوقع سلوك الكود غير المتزامن بدقة.
| 1 | console.log("1"); // متزامن، فوري |
| 2 | |
| 3 | setTimeout(() => { |
| 4 | console.log("2"); // غير متزامن، يذهب لطابور المهام |
| 5 | }, 0); |
| 6 | |
| 7 | Promise.resolve().then(() => { |
| 8 | console.log("3"); // غير متزامن، لكن Microtask بأولوية أعلى |
| 9 | }); |
| 10 | |
| 11 | console.log("4"); // متزامن، فوري |
| 12 | |
| 13 | // الترتيب الفعلي في الطباعة: 1, 4, 3, 2 |
مثال 1: إثبات أولوية الكود المتزامن
| 1 | // إثبات: كل الكود المتزامن ينتهي أولاً مهما كان التأخير صفراً |
| 2 | console.log("بداية البرنامج"); |
| 3 | |
| 4 | setTimeout(() => { |
| 5 | console.log("من setTimeout بتأخير 0"); |
| 6 | }, 0); |
| 7 | |
| 8 | for (let i = 0; i < 3; i++) { |
| 9 | console.log("حلقة متزامنة:", i); |
| 10 | } |
| 11 | |
| 12 | console.log("نهاية الكود المتزامن"); |
| 13 | // setTimeout يُطبع دائماً في النهاية رغم تأخيره صفر |
مثال 2: أولوية Microtask Queue على Task Queue
| 1 | // أولوية Microtasks (Promises) على Task Queue العادي (setTimeout) |
| 2 | setTimeout(() => console.log("Timeout"), 0); |
| 3 | Promise.resolve().then(() => console.log("Promise")); |
| 4 | console.log("متزامن"); |
| 5 | |
| 6 | // الترتيب: "متزامن" ثم "Promise" ثم "Timeout" |
| 7 | // رغم أن كليهما غير متزامن، الـ Promise له أولوية أعلى |