تم إنشاء Node في الأصل لبناء خوادم ويب عالية الأداء باعتبارها وقت تشغيل من جانب الخادم لـ JavaScript، فهي تحتوي على ميزات مثل الإدخال/الإخراج غير المتزامن ومؤشر الترابط الفردي. يمكّن نموذج البرمجة غير المتزامن المستند إلى حلقة الحدث العقدة من التعامل مع التزامن العالي ويحسن أداء الخادم بشكل كبير. وفي الوقت نفسه، نظرًا لأنه يحافظ على خصائص الترابط الفردي لجافا سكريبت، لا تحتاج Node إلى التعامل مع مشكلات مثل مزامنة الحالة و. حالة توقف تام ضمن مؤشرات الترابط المتعددة لا يوجد أي عبء أداء ناتج عن تبديل سياق مؤشر الترابط. بناءً على هذه الخصائص، تتمتع Node بالمزايا الكامنة في الأداء العالي والتزامن العالي، ويمكن بناء العديد من منصات تطبيقات الشبكة عالية السرعة والقابلة للتطوير بناءً عليها.
ستتعمق هذه المقالة في آلية التنفيذ والتنفيذ الأساسية لحلقة الأحداث والأحداث غير المتزامنة في العقدة، وآمل أن تكون مفيدة لك.
لماذا تستخدم Node النموذج غير المتزامن كنموذج برمجي أساسي لها؟
كما ذكرنا من قبل، تم إنشاء Node في الأصل لبناء خوادم ويب عالية الأداء، بافتراض أن هناك عدة مجموعات من المهام غير المرتبطة التي يجب إكمالها في سيناريو الأعمال، هناك حلان رئيسيان حديثان:
التنفيذ التسلسلي المفرد.
اكتمل بالتوازي مع خيوط متعددة.
التنفيذ التسلسلي أحادي الخيط هو نموذج برمجة متزامن، على الرغم من أنه يتماشى أكثر مع طريقة تفكير المبرمج بالتسلسل ويسهل كتابة تعليمات برمجية أكثر ملاءمة، لأنه ينفذ عمليات الإدخال/الإخراج بشكل متزامن، إلا أنه يمكنه معالجة عمليات الإدخال/الإخراج فقط. في نفس الوقت، سيؤدي طلب واحد إلى استجابة الخادم ببطء ولا يمكن تطبيقه في سيناريوهات التطبيقات عالية التزامن. علاوة على ذلك، لأنه يحظر الإدخال/الإخراج، ستنتظر وحدة المعالجة المركزية دائمًا حتى يكتمل الإدخال/الإخراج ولا يمكنها القيام بذلك أشياء أخرى، والتي ستحد من قوة معالجة وحدة المعالجة المركزية ليتم استخدامها بالكامل، ستؤدي في النهاية إلى انخفاض الكفاءة،
كما أن نموذج البرمجة متعدد الخيوط سيسبب للمطورين مشاكل مثل مزامنة الحالة والجمود في البرمجة. على الرغم من أن الخيوط المتعددة يمكنها تحسين استخدام وحدة المعالجة المركزية بشكل فعال على وحدات المعالجة المركزية متعددة النواة.
على الرغم من أن نموذج البرمجة للتنفيذ التسلسلي أحادي الخيوط والتنفيذ المتوازي متعدد الخيوط له مزاياه الخاصة، إلا أنه يعاني أيضًا من عيوب من حيث الأداء وصعوبة التطوير.
بالإضافة إلى ذلك، بدءاً من سرعة الاستجابة لطلبات العميل، إذا حصل العميل على موردين في نفس الوقت، فإن سرعة استجابة الطريقة المتزامنة ستكون مجموع سرعات استجابة الموردين، وسرعة استجابة الطريقة المتزامنة الطريقة غير المتزامنة ستكون في منتصف الطريقتين الأكبر، وميزة الأداء واضحة جدًا مقارنة بالمزامنة. مع زيادة تعقيد التطبيق، سيتطور هذا السيناريو إلى الاستجابة لطلبات n في نفس الوقت، وسيتم تسليط الضوء على مزايا غير المتزامن مقارنة بالمزامنة.
لتلخيص ذلك، تقدم Node إجابتها: استخدم مؤشر ترابط واحد للابتعاد عن حالة الجمود متعدد الخيوط ومزامنة الحالة والمشكلات الأخرى؛ استخدم الإدخال / الإخراج غير المتزامن لإبقاء مؤشر ترابط واحد بعيدًا عن الحظر لاستخدام وحدة المعالجة المركزية بشكل أفضل. ولهذا السبب تستخدم Node النموذج غير المتزامن كنموذج برمجي أساسي لها.
بالإضافة إلى ذلك، من أجل تعويض النقص في مؤشر ترابط واحد لا يمكنه استخدام وحدات المعالجة المركزية متعددة النواة، توفر Node أيضًا عملية فرعية مشابهة لعمال الويب في المتصفح، والتي يمكنها استخدام وحدة المعالجة المركزية بكفاءة من خلال العمليات المنفذة.
بعد الحديث عن سبب استخدامنا غير المتزامن، كيف نطبق غير المتزامن؟
هناك نوعان من العمليات غير المتزامنة التي نطلق عليها عادةً: أحدهما العمليات المتعلقة بالإدخال/الإخراج مثل إدخال/إخراج الملف وإدخال/إخراج الشبكة؛ والآخر هو العمليات غير المرتبطة بالإدخال/الإخراج مثل setTimeOut
و setInterval
. من الواضح أن العمليات غير المتزامنة التي نناقشها تشير إلى العمليات المتعلقة بالإدخال/الإخراج، أي الإدخال/الإخراج غير المتزامن.
تم اقتراح الإدخال/الإخراج غير المتزامن على أمل ألا تمنع مكالمات الإدخال/الإخراج تنفيذ البرامج اللاحقة، وسيتم تخصيص الوقت الأصلي لانتظار اكتمال الإدخال/الإخراج للشركات الأخرى المطلوبة للتنفيذ. لتحقيق هذا الهدف، تحتاج إلى استخدام الإدخال/الإخراج غير المحظور.
حظر الإدخال/الإخراج يعني أنه بعد أن تبدأ وحدة المعالجة المركزية في استدعاء الإدخال/الإخراج، سيتم حظرها حتى اكتمال الإدخال/الإخراج. من خلال معرفة حظر الإدخال/الإخراج، من السهل فهم الإدخال/الإخراج غير المحظور، وستعود وحدة المعالجة المركزية فورًا بعد بدء مكالمة الإدخال/الإخراج بدلاً من الحظر والانتظار. من الواضح أنه بالمقارنة مع حظر الإدخال/الإخراج، فإن الإدخال/الإخراج غير المحظور لديه المزيد من التحسينات في الأداء.
لذا، نظرًا لاستخدام الإدخال/الإخراج غير المحظور ويمكن لوحدة المعالجة المركزية العودة فورًا بعد بدء استدعاء الإدخال/الإخراج، فكيف تعرف أن الإدخال/الإخراج قد اكتمل؟ الجواب هو الاقتراع
من أجل الحصول على حالة مكالمات الإدخال/الإخراج في الوقت المناسب، ستقوم وحدة المعالجة المركزية باستدعاء عمليات الإدخال/الإخراج بشكل متكرر للتأكد من اكتمال عملية الإدخال/الإخراج. وتسمى تقنية الاستدعاءات المتكررة هذه لتحديد ما إذا كانت العملية قد اكتملت أم لا، بالاقتراع .
من الواضح أن الاستقصاء سيؤدي إلى قيام وحدة المعالجة المركزية بتنفيذ أحكام الحالة بشكل متكرر، وهو ما يعد مضيعة لموارد وحدة المعالجة المركزية. علاوة على ذلك، من الصعب التحكم في الفاصل الزمني للاستقصاء إذا كان الفاصل الزمني طويلا جدا، فلن يتلقى إكمال عملية الإدخال / الإخراج استجابة في الوقت المناسب، مما يقلل بشكل غير مباشر من سرعة استجابة التطبيق، إذا كان الفاصل الزمني قصيرا جدا؛ سيتم حتماً إنفاق وحدة المعالجة المركزية على الاستقصاء، فهي تستغرق وقتًا أطول وتقلل من استخدام موارد وحدة المعالجة المركزية.
لذلك، على الرغم من أن الاستقصاء يفي بمتطلبات عدم حظر عمليات الإدخال/الإخراج غير المحظورة تنفيذ البرامج اللاحقة، بالنسبة للتطبيق، لا يزال من الممكن اعتباره نوعًا من المزامنة فقط، لأن التطبيق لا يزال بحاجة إلى انتظار الإدخال/الإخراج يا للعودة تماما لا يزال قضى الكثير من الوقت في الانتظار.
يجب أن يكون الإدخال/الإخراج غير المتزامن المثالي هو أن يبدأ التطبيق مكالمة غير محظورة، وليست هناك حاجة للاستعلام المستمر عن حالة مكالمة الإدخال/الإخراج من خلال الاستقصاء اكتمل الإدخال/الإخراج، ما عليك سوى تمرير البيانات إلى التطبيق من خلال الإشارة أو رد الاتصال.
كيفية تنفيذ هذا الإدخال/الإخراج غير المتزامن؟ الجواب هو تجمع الخيوط
على الرغم من أن هذه المقالة تشير دائمًا إلى أن العقدة يتم تنفيذها في سلسلة رسائل واحدة، إلا أن السلسلة الفردية هنا تعني أن كود JavaScript يتم تنفيذه في سلسلة رسائل واحدة، بالنسبة لأجزاء مثل عمليات الإدخال/الإخراج التي لا علاقة لها بمنطق العمل الرئيسي. عن طريق التنفيذ الآخر في شكل سلاسل لن يؤثر على تشغيل الخيط الرئيسي أو يمنعه، بل على العكس من ذلك، يمكنه تحسين كفاءة تنفيذ الخيط الرئيسي وتحقيق الإدخال / الإخراج غير المتزامن.
من خلال تجمع مؤشرات الترابط، اسمح للخيط الرئيسي بإجراء مكالمات الإدخال / الإخراج فقط، والسماح لسلاسل الرسائل الأخرى بإجراء حظر الإدخال / الإخراج أو عدم حظر الإدخال / الإخراج بالإضافة إلى تقنية الاستقصاء لإكمال الحصول على البيانات، ثم استخدم الاتصال بين سلاسل الرسائل لإكمال عملية الإدخال / الإخراج /O يتم تمرير البيانات التي تم الحصول عليها، والتي تنفذ بسهولة الإدخال / الإخراج غير المتزامن:
يقوم مؤشر الترابط الرئيسي بإجراء مكالمات الإدخال / الإخراج، بينما يقوم تجمع مؤشرات الترابط بإجراء عمليات الإدخال / الإخراج، وإكمال الحصول على البيانات، ثم تمرير البيانات إلى مؤشر الترابط الرئيسي من خلال الاتصال بين سلاسل الرسائل لإكمال استدعاء الإدخال / الإخراج، والخيط الرئيسي إعادة الاستخدام تعرض وظيفة رد الاتصال البيانات للمستخدم، الذي يستخدم البيانات بعد ذلك لإكمال العمليات على مستوى منطق الأعمال. هذه عملية إدخال/إخراج غير متزامنة كاملة في Node. بالنسبة للمستخدمين، لا داعي للقلق بشأن تفاصيل التنفيذ المرهقة للطبقة الأساسية، فهم يحتاجون فقط إلى استدعاء واجهة برمجة التطبيقات غير المتزامنة المغلفة بواسطة Node وتمرير وظيفة رد الاتصال التي تتعامل مع منطق الأعمال، كما هو موضح أدناه:
const fs = require. ("خ.س")؛ fs.readFile('example.js', (data) => { // معالجة منطق الأعمال});
تختلف آلية التنفيذ الأساسية غير المتزامنة لـ Nodejs على الأنظمة الأساسية المختلفة: في نظام التشغيل Windows، يُستخدم IOCP بشكل أساسي لإرسال مكالمات الإدخال/الإخراج إلى kernel النظام والحصول على عمليات الإدخال/الإخراج المكتملة من kernel مع حلقة حدث لإكمال عملية الإدخال/الإخراج غير المتزامنة، يتم تنفيذ هذه العملية من خلال epoll ضمن Linux؛ ومن خلال kqueue ضمن FreeBSD، ومن خلال منافذ الأحداث ضمن Solaris. يتم توفير تجمع الخيوط مباشرة بواسطة kernel (IOCP) ضمن Windows، بينما يتم تنفيذ سلسلة *nix
بواسطة libuv نفسه.
نظرًا للاختلاف بين نظام Windows الأساسي ومنصة *nix
، توفر Node libuv كطبقة تغليف مجردة، بحيث تكتمل جميع أحكام توافق النظام الأساسي بواسطة هذه الطبقة، مما يضمن توافق العقدة ذات الطبقة العليا وتجمع الخيوط المخصصة للطبقة السفلية وIOCP مستقلة عن بعضها البعض. ستحدد Node شروط النظام الأساسي أثناء الترجمة وتجميع الملفات المصدر بشكل انتقائي في دليل Unix أو دليل Win في البرنامج الهدف:
ما ورد أعلاه هو تطبيق العقدة غير المتزامن.
(يمكن تعيين حجم تجمع الخيوط من خلال متغير البيئة UV_THREADPOOL_SIZE
. القيمة الافتراضية هي 4. يمكن للمستخدم ضبط حجم هذه القيمة بناءً على الوضع الفعلي.)
ثم السؤال هو، بعد الحصول على البيانات التي تم تمريرها بواسطة كيف يتم استدعاء مؤشر الترابط الرئيسي عندما يتم استدعاء وظيفة رد الاتصال؟ الجواب هو حلقة الحدث.
نظرًا لأنيستخدم وظائف رد الاتصال لمعالجة بيانات الإدخال/الإخراج، فإنه يتضمن حتماً مسألة متى وكيف يتم استدعاء وظيفة رد الاتصال. في التطوير الفعلي، غالبًا ما تتضمن سيناريوهات استدعاءات الإدخال/الإخراج غير المتزامنة العديدة والمتعددة الأنواع مشكلة صعبة، بالإضافة إلى كيفية ترتيب مكالمات عمليات رد الاتصال غير المتزامنة هذه وضمان التقدم المنظم لعمليات الاسترجاعات غير المتزامنة الإدخال/الإخراج غير المتزامن بالإضافة إلى /O، هناك أيضًا مكالمات غير متزامنة للإدخال/الإخراج مثل أجهزة ضبط الوقت. تعتبر واجهات برمجة التطبيقات هذه في الوقت الفعلي للغاية ولها أولويات أعلى في المقابل. كيفية جدولة عمليات الاسترجاعات بأولويات مختلفة؟
لذلك يجب أن تكون هناك آلية جدولة لتنسيق المهام غير المتزامنة ذات الأولويات والأنواع المختلفة لضمان تشغيل هذه المهام بطريقة منظمة على الخيط الرئيسي. مثل المتصفحات، اختارت Node حلقة الحدث للقيام بهذا العمل الثقيل.
تقوم Node بتقسيم المهام إلى سبع فئات وفقًا لنوعها وأولويتها: المؤقتات، والمعلقة، والخمول، والتحضير، والاستقصاء، والتحقق، والإغلاق. لكل نوع من المهام، هناك قائمة انتظار مهام الوارد أولاً يخرج أولاً لتخزين المهام وعمليات رد الاتصال الخاصة بها (يتم تخزين المؤقتات في كومة علوية صغيرة). بناءً على هذه الأنواع السبعة، تقسم Node تنفيذ حلقة الحدث إلى المراحل السبع التالية:
أولوية تنفيذ هذه المرحلة من
في هذه المرحلة، ستتحقق حلقة الحدث من بنية البيانات (الحد الأدنى من الكومة) التي تخزن المؤقت، وتجتاز المؤقتات فيه، وتقارن الوقت الحالي ووقت انتهاء الصلاحية واحدًا تلو الآخر، وتحدد ما إذا كان المؤقت قد انتهى أم لا ، سيتم إخراج وظيفة رد الاتصال وتنفيذها.
ستقوم المرحلةبتنفيذ عمليات الاسترجاعات عند حدوث الشبكة والإدخال والاستثناءات الأخرى. ستتم معالجة بعض الأخطاء التي أبلغ عنها *nix
في هذه المرحلة. بالإضافة إلى ذلك، سيتم تأجيل بعض عمليات الاسترجاعات للإدخال/الإخراج التي يجب تنفيذها في مرحلة الاستقصاء للدورة السابقة إلى هذه المرحلة.
فقط داخل حلقة الحدث.
يستردأحداث الإدخال/الإخراج الجديدة؛ وينفذ عمليات رد الاتصال المتعلقة بالإدخال/الإخراج (جميع عمليات رد الاتصال تقريبًا باستثناء عمليات رد الاتصال الخاصة بإيقاف التشغيل، وعمليات رد الاتصال المجدولة بالمؤقت، و setImmediate()
)؛
الاستقصاء، أي أن مرحلة الاستقصاء هي أهم مرحلة في حلقة الحدث، حيث تتم معالجة عمليات الاسترجاعات الخاصة بإدخال/إخراج الشبكة وإدخال/إخراج الملف بشكل أساسي في هذه المرحلة. تحتوي هذه المرحلة على وظيفتين رئيسيتين:
حساب المدة التي يجب أن يتم خلالها حظر هذه المرحلة واستقصاء الإدخال/الإخراج.
التعامل مع عمليات الاسترجاعات في قائمة انتظار الإدخال/الإخراج.
عندما تدخل حلقة الحدث مرحلة الاستقصاء ولم يتم تعيين مؤقت:
إذا لم تكن قائمة انتظار الاستقصاء فارغة، فستجتاز حلقة الحدث قائمة الانتظار، وتنفذها بشكل متزامن حتى تصبح قائمة الانتظار فارغة أو يتم الوصول إلى الحد الأقصى للعدد الذي يمكن تنفيذه.
إذا كانت قائمة انتظار الاستقصاء فارغة، فسيحدث أحد أمرين آخرين:
إذا كان هناك رد اتصال setImmediate()
يجب تنفيذه، فستنتهي مرحلة الاستقصاء على الفور ويتم إدخال مرحلة التحقق لتنفيذ رد الاتصال.
إذا لم يكن هناك أي رد اتصال setImmediate()
ليتم تنفيذه، فستبقى حلقة الحدث في هذه المرحلة في انتظار إضافة ردود الاتصال إلى قائمة الانتظار ثم تنفيذها على الفور. ستنتظر حلقة الحدث حتى انتهاء المهلة. السبب وراء اختياري للتوقف هنا هو أن Node تتعامل بشكل أساسي مع عمليات الإدخال والإخراج، بحيث يمكنها الاستجابة لعمليات الإدخال والإخراج في الوقت المناسب.
بمجرد أن تصبح قائمة انتظار الاستقصاء فارغة، تقوم حلقة الحدث بالتحقق من أجهزة ضبط الوقت التي وصلت إلى الحد الزمني الخاص بها. إذا وصل مؤقت واحد أو أكثر إلى الحد الزمني، فستعود حلقة الحدث إلى مرحلة المؤقتات لتنفيذ عمليات الاسترجاعات لهذه المؤقتات.
ستنفذالمرحلة عمليات الاسترجاعات الخاصة بـ setImmediate()
بالتسلسل.
ستنفذ هذه المرحلة بعض عمليات الاسترجاعات لإغلاق الموارد، مثل socket.on('close', ...)
. سيكون لتأخير تنفيذ هذه المرحلة تأثير ضئيل وسيكون له الأولوية الدنيا.
عندما تبدأ عملية العقدة، ستقوم بتهيئة حلقة الحدث، وتنفيذ كود الإدخال الخاص بالمستخدم، وإجراء استدعاءات واجهة برمجة التطبيقات غير المتزامنة المقابلة، وجدولة المؤقت، وما إلى ذلك، ثم البدء في الدخول إلى حلقة الحدث:
┌───────── ── ──────────────┐ ┌─>│ الموقتات │ │ └───────────┬─────────────┘ │ ┌───────────┴─────────────┐ │ │ عمليات الاسترجاعات المعلقة │ │ └───────────┬─────────────┘ │ ┌───────────┴─────────────┐ │ │ خاملاً ، استعد │ │ └─────────┬─────────┘ ┌────────── ──────┐ │ ┌───────────┴────────────┐ │ واردة: │ │ │ استطلاع │<─────┤ اتصالات ، │ │ └───────────┬─────────────┘ │ البيانات ، إلخ. │ │ ┌───────────┴───────────────── ──────┘ │ │ تحقق │ │ └───────────┬─────────────┘ │ ┌───────────┴─────────────┐ └──┤ إغلاق عمليات الاسترجاعات │ └───────────────────────────┘
كل تكرار لحلقة الحدث (غالبًا ما يسمى علامة التجزئة) سيكون على النحو المعطى أعلاه الأولوية يدخل الأمر في مراحل التنفيذ السبعة، وستنفذ كل مرحلة عددًا معينًا من عمليات الاسترجاعات في قائمة الانتظار. والسبب في تنفيذ عدد معين فقط وليس تنفيذ كل شيء هو منع وقت تنفيذ المرحلة الحالية من أن يكون طويلًا جدًا. تجنب فشل المرحلة التالية.
حسنًا، ما ورد أعلاه هو تدفق التنفيذ الأساسي لحلقة الحدث. الآن دعونا نلقي نظرة على سؤال آخر.
بالنسبة للسيناريو التالي:
const server = net.createServer(() => {}).listen(8080); server.on('listening', () => {});
عندما يتم ربط الخدمة بنجاح بالمنفذ 8000، أي عند استدعاء listen()
بنجاح، لم يتم ربط رد الاتصال بحدث listening
بعد، لذلك بعد ربط المنفذ بنجاح، لن يتم تنفيذ رد الاتصال لحدث listening
الذي مررناه.
بالتفكير في سؤال آخر، قد تكون لدينا بعض الاحتياجات أثناء التطوير، مثل معالجة الأخطاء وتنظيف الموارد غير الضرورية والمهام الأخرى ذات الأولوية المنخفضة. إذا تم تنفيذ هذه المنطق بطريقة متزامنة، فسوف يؤثر ذلك على كفاءة التنفيذ. إذا تم تمرير setImmediate()
بشكل غير متزامن، كما هو الحال في شكل ردود اتصال، فلا يمكن ضمان توقيت تنفيذها، والأداء في الوقت الحقيقي ليس عاليًا. فكيف التعامل مع هذه المنطق؟
بناءً على هذه المشكلات، أخذت Node مرجعًا من المتصفح ونفذت مجموعة من آليات المهام الصغيرة. في العقدة، بالإضافة إلى استدعاء new Promise().then()
سيتم أيضًا تغليف وظيفة رد الاتصال التي تم تمريرها في مهمة صغيرة. سيتم أيضًا تغليف رد الاتصال الخاص بـ process.nextTick()
في مهمة صغيرة، وأولوية التنفيذ للمهمة الدقيقة. الأخير سيكون أعلى من الأول.
مع المهام الدقيقة، ما هي عملية تنفيذ حلقة الحدث؟ بمعنى آخر، متى يتم تنفيذ المهام الدقيقة؟
في العقدة 11 والإصدارات الأحدث، بمجرد تنفيذ مهمة في مرحلة ما، يتم تنفيذ قائمة انتظار المهام الصغيرة على الفور ويتم مسح قائمة الانتظار.
يبدأ تنفيذ المهام الدقيقة بعد تنفيذ المرحلة قبل العقدة 11.
لذلك، مع المهام الدقيقة، ستقوم كل دورة من حلقة الحدث أولاً بتنفيذ مهمة في مرحلة المؤقتات، ثم قم بمسح قوائم انتظار المهام الصغيرة الخاصة بـ process.nextTick()
و new Promise().then()
بالترتيب، ثم تابع التنفيذ المهمة التالية في مرحلة المؤقتات أو المرحلة التالية، أي مهمة في المرحلة المعلقة، وهكذا بهذا الترتيب.
باستخدام process.nextTick()
، يمكن للعقدة حل مشكلة ربط المنفذ أعلاه: داخل طريقة listen()
، سيتم تغليف إصدار حدث listening
في رد اتصال وتمريره إلى process.nextTick()
، كما هو موضح في الشكل الزائف التالي الكود:
وظيفة الاستماع () { // تنفيذ عمليات منفذ الاستماع... // قم بتغليف إصدار حدث "الاستماع" في رد اتصال وتمريره إلى "process.nextTick()" في العملية.nextTick(() => { تنبعث('الاستماع'); }); };
بعد تنفيذ التعليمات البرمجية الحالية، سيبدأ تنفيذ المهمة الدقيقة، وبالتالي إصدار حدث listening
وبدء استدعاء رد اتصال الحدث.
نظرًا لعدم القدرة على التنبؤ وتعقيد العمل غير المتزامن نفسه، أثناء عملية استخدام واجهة برمجة التطبيقات غير المتزامنة التي توفرها Node، على الرغم من أننا أتقننا مبدأ تنفيذ حلقة الحدث، إلا أنه قد لا تزال هناك بعض الظواهر غير البديهية أو المتوقعة. .
على سبيل المثال، ترتيب تنفيذ المؤقتات ( setTimeout
, setImmediate
) سيختلف وفقًا للسياق الذي يتم استدعاؤهم فيه. إذا تم استدعاء كليهما من سياق المستوى الأعلى، فإن وقت التنفيذ يعتمد على أداء العملية أو الجهاز.
لنلقِ نظرة على المثال التالي:
setTimeout(() => { console.log('المهلة'); }, 0); setImmediate(() => { console.log('فوري'); });
ما هي نتيجة تنفيذ الكود أعلاه؟ وفقًا لوصفنا لحلقة الحدث الآن، قد يكون لديك هذه الإجابة: نظرًا لأنه سيتم تنفيذ مرحلة المؤقتات قبل مرحلة التحقق، فسيتم تنفيذ رد الاتصال لـ setTimeout()
أولاً، ثم سيتم تنفيذ رد الاتصال لـ setImmediate()
تم تنفيذه.
في الواقع، نتيجة الإخراج لهذا الرمز غير مؤكدة قد يتم إخراج المهلة أولاً، أو قد يتم إخراج فوري أولاً. وذلك لأنه يتم استدعاء كلا المؤقتين في السياق العام. عندما تبدأ حلقة الحدث في التشغيل والتنفيذ إلى مرحلة المؤقتات، قد يكون الوقت الحالي أكبر من 1 مللي ثانية أو أقل من 1 مللي ثانية، اعتمادًا على أداء التنفيذ للجهاز ، فمن غير المؤكد في الواقع setTimeout()
في مرحلة الموقتات الأولى، لذلك ستظهر نتائج مخرجات مختلفة.
(عندما تكون قيمة delay
(المعلمة الثانية لـ setTimeout
) أكبر من 2147483647
أو أقل من 1
، فسيتم تعيين delay
على 1
)
لنلقِ نظرة على الكود التالي:
const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('المهلة'); }, 0); setImmediate(() => { console.log('فوري'); }); });
يمكن ملاحظة أنه في هذا الكود، يتم تغليف كلا المؤقتين في وظائف رد الاتصال وتمريرهما إلى readFile
. ومن الواضح أنه عند استدعاء رد الاتصال، يجب أن يكون الوقت الحالي أكبر من 1 مللي ثانية، لذلك سيتم رد الاتصال الخاص بـ setTimeout
. يكون أطول من رد الاتصال الخاص بـ setImmediate
حيث يتم استدعاء رد الاتصال أولاً، وبالتالي فإن النتيجة المطبوعة هي: timeout immediate
.
ما ورد أعلاه هو أشياء تتعلق بالمؤقتات التي يجب عليك الانتباه إليها عند استخدام Node. بالإضافة إلى ذلك new Promise().then()
تحتاج setImmediate()
إلى الانتباه إلى ترتيب تنفيذ process.nextTick()
.
: تبدأ المقالة بشرح أكثر تفصيلاً لمبادئ تنفيذ حلقة حدث العقدة من وجهتي نظر حول سبب الحاجة إلى غير متزامن وكيفية تنفيذ غير متزامن، وتذكر بعض الأمور ذات الصلة التي تحتاج إلى الاهتمام أنت.