هل تعرف إيفان تولوب؟ على الأرجح نعم ، أنت لا تعرف حتى الآن أي نوع من الأشخاص هذا ، وتحتاج إلى رعاية كبيرة لحالة نظام القلب والأوعية الدموية.
حول هذا وكيف يعمل التزامن في JS تحت غطاء المحرك ، وكيف يعمل Event Loop في المتصفحات و Node.js ، هل هناك أي اختلافات وربما تم ذكر أشياء مماثلة بواسطة
Mikhail Bashurov (
SaitoNakamura ) في تقريره عن RIT ++. يسعدنا أن نطلعكم على نص هذا العرض الإعلامي.
حول المتحدث: ميخائيل باشوروف هو مطور ويب متكامل على JS و .NET من Luxoft. يحب واجهة المستخدم الجميلة ، الاختبارات الخضراء ، transpilation ، compiler ، مترجم يسمح بتقنية وتحسين تجربة ديف.
ملاحظة المحرر: لم يكن تقرير ميخائيل مصحوبًا بشرائح فحسب ، بل بمشروع تجريبي يمكنك من خلاله النقر على الأزرار ومشاهدة تنفيذ المراوغة بشكل مستقل. سيكون الخيار الأفضل هو فتح
العرض التقديمي في علامة تبويب مجاورة والإشارة إليه بشكل دوري ، ولكن النص سيوفر أيضًا روابط لصفحات محددة. والآن نمرر الكلمة للمتكلم ، استمتع بالقراءة.
جد إيفان تولوب
كان لدي ترشيح لإيفان تولوب.

لكنني قررت أن أذهب في مسار أكثر امتثالًا ، لذا قابل - جد إيفان تولوب!

في الواقع ، هناك شيئان فقط يحتاجان إلى معرفته عنه:
- يحب لعب الورق.
- هو ، مثل كل الناس ، لديه قلب ، وهو ينبض.
حقائق النوبة القلبية
ربما سمعت أن حالات أمراض القلب والوفيات الناجمة عنها أصبحت أكثر تكرارًا في الآونة الأخيرة. ربما يكون أكثر أمراض القلب شيوعًا هو النوبة القلبية ، أي النوبة القلبية.
ما هو المثير للاهتمام حول النوبة القلبية؟
- في معظم الأحيان ، يحدث صباح يوم الاثنين.
- في الأشخاص العازبين ، يكون خطر الإصابة بنوبة قلبية أعلى مرتين. هنا ، ربما ، النقطة هي فقط في الارتباط ، وليس في علاقة سببية. لسوء الحظ (أو لحسن الحظ) ، هذا صحيح.
- توفي عشرة موصلات بأزمة قلبية أثناء إجراء (على ما يبدو عمل عصبي للغاية!).
- النوبة القلبية هي نخر في عضلة القلب بسبب نقص تدفق الدم.
لدينا شريان تاجي ينقل الدم إلى العضلة (عضلة القلب). إذا بدأ الدم في التدفق بشكل سيئ ، تموت العضلات تدريجيًا. بطبيعة الحال ، هذا له تأثير سلبي للغاية على القلب وعمله.
جد إيفان تولوب لديه قلب أيضًا ، وهو ينبض. لكن قلبنا يضخ الدم ، وقلب إيفان تولوب يضخ رمزنا وأحزاننا.
تاسكي: دائرة كبيرة من الدورة الدموية
ما هي المهام؟ ما الذي يمكن أن يكون كسولًا بشكل عام في المتصفح؟ لماذا هم في حاجة ماسة؟
على سبيل المثال ، نقوم بتنفيذ التعليمات البرمجية من برنامج نصي. هذه نبضة قلب ، والآن لدينا تدفق الدم. لقد نقرنا على الزر واشتركنا في الحدث - معالج الحدث لهذا الحدث بصق - رد الاتصال الذي أرسلناه. لقد قاموا بتعيين Timeout ، Callback يعمل - مهمة أخرى. وهكذا في أجزاء ، نبضة قلب واحدة هي مهمة واحدة.

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

- هناك مهمة ، أكملناها.
- ثم نقوم بتنفيذ تقديم المستعرض.
ولكن في الواقع ، هذا ليس ضروريًا ، لأنه في بعض الحالات قد لا يقوم المتصفح بتقديم بين مهمتين.
يمكن أن يحدث هذا ، على سبيل المثال ، إذا كان بإمكان المتصفح أن يقرر تجميع مهلات متعددة أو أحداث تمرير متعددة. أو في وقت ما ، حدث خطأ ما ، ويقرر المتصفح بدلاً من 60 إطارًا في الثانية (معدل الإطار العادي بحيث يصبح كل شيء باردًا وسلسًا) لإظهار 30 إطارًا في الثانية. وبالتالي ، سيكون لديه المزيد من الوقت لتنفيذ التعليمات البرمجية الخاصة بك وغيرها من الأعمال المفيدة ، وسوف يكون قادرا على أداء العديد من الصدمات.
لذلك ، لا يتم تنفيذ التقديم بعد كل مهمة.
تاسكي: التصنيف
هناك نوعان من العمليات المحتملة:
- I / O مقيد ؛
- وحدة المعالجة المركزية ملزمة.
وحدة المعالجة المركزية هي عملنا المفيد الذي نقوم به (نعتقد ، العرض ، إلخ.)
I / O هي النقاط التي يمكننا عندها مشاركة مهامنا. يمكن أن يكون:
لقد صنعنا setTimeout 5000 مللي ثانية ، وننتظر فقط 5000 مللي ثانية ، ولكن يمكننا القيام بعمل مفيد آخر. فقط عندما يمر هذا الوقت ، نحصل على رد الاتصال ، ونقوم ببعض العمل فيه.
ذهبنا عبر الإنترنت. بينما ننتظر استجابة من الشبكة ، نحن ننتظر فقط ، ولكن يمكننا أيضًا القيام بشيء مفيد.
أو ، على سبيل المثال ، نذهب إلى Network BD. نحن نتحدث أيضًا عن Node.js ، بما في ذلك ، وإذا أردنا الذهاب إلى مكان ما على الشبكة من Node.js من فضلك - هذه هي نفس المهمة المحتملة للإدخال / الإخراج (الإدخال / الإخراج).
اقرأ الملف - من المحتمل أنه ليس مهمة مرتبطة بوحدة المعالجة المركزية على الإطلاق. في Node.js ، يتم تشغيله في تجمع مؤشرات الترابط بسبب واجهة برمجة تطبيقات Linux ملتوية قليلاً ، لنكون صادقين.
ثم CPUbound هو:
- على سبيل المثال ، عندما نقوم بحلقة for / for (؛؛) أو نمر عبر المصفوفة بطريقة ما باستخدام طرق إضافية: التصفية ، الخريطة ، إلخ.
- JSON.parse أو JSON.stringify ، أي تسلسل الرسائل / إلغاء التسلسل. كل هذا يتم على وحدة المعالجة المركزية ، لا يمكننا فقط الانتظار حتى يتم تنفيذ كل شيء بطريقة سحرية في مكان ما.
- عد التجزئات ، على سبيل المثال ، تعدين التشفير.
بالطبع ، يمكن أيضًا تعدين التشفير على GPU ، ولكن أعتقد - GPU ، CPU - أنت تفهم هذا التشبيه.
تاسكي: عدم انتظام ضربات القلب والخثرة
ونتيجة لذلك ، تبين أن نبض قلبنا: يقوم بمهمة واحدة ، والثانية ، والثالثة - حتى نفعل شيئًا خاطئًا. على سبيل المثال ، نمر بمجموعة من مليون عنصر ونحسب المجموع. يبدو أن هذا ليس بالأمر الصعب ، ولكن يمكن أن يستغرق وقتًا ملموسًا. إذا أخذنا وقتًا ملموسًا باستمرار دون تحرير المهمة ، فلا يمكن إجراء التجسيد. تحوم في هذا الشوق ، ويبدأ كل عدم انتظام ضربات القلب.
أعتقد أن الجميع يفهم أن عدم انتظام ضربات القلب هو مرض قلبي مزعج إلى حد ما. ولكن لا يزال بإمكانك العيش معه. ماذا يحدث إذا وضعت مهمة معلقة ببساطة "حلقة الأحداث" بأكملها في حلقة لا نهائية؟ أنت تضع نوعًا ما جلطة دموية في الشريان التاجي أو أي شريان آخر ، وسيصبح كل شيء حزينًا تمامًا. لسوء الحظ ، سيموت جدنا إيفان تولوب.
توفي جد إيفان ...

بالنسبة لنا ، هذا يعني أن علامة التبويب بأكملها تتجمد تمامًا - لا يمكنك النقر على أي شيء ، ثم يقول Chrome: "عذرًا!"
هذا أسوأ بكثير من أخطاء الموقع عندما حدث خطأ ما. ولكن إذا تم تعليق كل شيء على الإطلاق ، وحتى ، على الأرجح ، تم تحميل وحدة المعالجة المركزية وشنق المستخدم بشكل عام ، فمن المرجح ألا يذهب إلى موقعك مرة أخرى.
وبالتالي ، فإن الفكرة هي: لدينا مهمة ، ولسنا بحاجة إلى تعليق هذه المهمة لفترة طويلة جدًا. نحتاج إلى تحريرها بسرعة ، حتى يتمكن المتصفح ، إن وجد ، من العرض (إذا أراد). إذا كنت لا تريد - عظيم ، ارقص!
فيليب روبرتس ديمو: العدسة فيليب فيليبس
فكر
في مثال :
$.on('button', 'click', function onClick(){ console.log('click'); }); setTimeout(function timeout() { console log("timeout"); }. 5000); console.log(“Hello world");
الجوهر هو هذا: لدينا زر ، نشترك فيه (addEventListener) ، يتم استدعاء Timeout لمدة 5 ثوانٍ وعلى الفور في وحدة التحكم. مدونة نكتب "Hello ، world!" ، في setTimeout نكتب Timeout ، في onClick نكتب Click.
ماذا سيحدث إذا قمنا بتشغيله ومرات عديدة نضغط على الزر - متى سيتم تنفيذ المهلة بالفعل؟ دعنا نرى العرض التوضيحي:
يبدأ تنفيذ التعليمات البرمجية ، يحصل على المكدس ، يذهب Timeout. في هذه الأثناء ، نقرنا على الزر. في أسفل قائمة الانتظار ، تمت إضافة العديد من الأحداث. أثناء تشغيل Click ، تنتظر المهلة ، على الرغم من مرور 5 ثوانٍ.
هنا ، onClick سريع ، ولكن إذا وضعت مهمة أطول ، فسيتم تجميد كل شيء ، كما هو موضح بالفعل. هذا مثال مبسط للغاية. هنا منعطف واحد ، ولكن في المتصفحات ، في الواقع ، كل شيء ليس كذلك.
بأي ترتيب يتم تنفيذ الأحداث - ماذا تقول مواصفات HTML؟
تقول ما يلي: لدينا مفهومان:
- مصدر المهمة
- قائمة انتظار المهام.
مصدر المهمة هو نوع من المهمة. قد يكون هذا تفاعل المستخدم ، أي onClick ، onChange - وهو شيء يتفاعل معه المستخدم ؛ أو المؤقتات ، أي setTimeout و setInterval أو PostMessages ؛ أو حتى الأنواع البرية تمامًا مثل مصدر مهمة Canvas Blob Serialization - أيضًا نوع منفصل.
تقول المواصفات أنه بالنسبة لنفس مهام مصدر المهام ، سيتم ضمان تنفيذها بالترتيب الذي تتم إضافتها. لكل شيء آخر ، لا شيء مضمون ، لأنه يمكن أن يكون هناك عدد غير محدود من قوائم انتظار المهام. المستعرض يقرر كم سيكون هناك. بمساعدة قائمة انتظار المهام وإنشائها ، يمكن للمتصفح تحديد أولويات مهام معينة.
أولويات المتصفح وقوائم المهام

تخيل أن لدينا 3 خطوط:
- تفاعل المستخدم ؛
- مهلة
- نشر الرسائل.
يبدأ المتصفح في الحصول على المهام من قوائم الانتظار هذه:
- أولاً ، يأخذ تركيز المستخدم على التركيز - وهذا أمر مهم للغاية - فقد ضربات قلب واحدة.
- ثم يأخذ postMessages - حسنًا ، postMessages أولوية عالية جدًا ، رائع!
- الخطوة التالية ، onChange ، هي أيضًا مرة أخرى من تفاعل المستخدم في الأولوية.
- يتم إرسال onClick التالي. انتهت قائمة انتظار تفاعل المستخدم ، وعرضنا للمستخدم كل ما هو مطلوب.
- ثم نأخذ setInterval ، إضافة postMessages.
- سيتم تنفيذ setTimeout فقط الأحدث . كان في مكان ما في نهاية الخط.
هذا مرة أخرى مثال مبسط للغاية ، ولسوء الحظ ،
لا يمكن لأحد أن يضمن كيف سيعمل هذا في المتصفحات ، لأنهم يقررون كل هذا بأنفسهم. تحتاج إلى اختبار هذا بنفسك إذا كنت تريد معرفة ما هو.
على سبيل المثال ، تأخذ postMessages الأسبقية على setTimeout. ربما تكون قد سمعت عن شيء مثل setImmediate ، على سبيل المثال ، في متصفحات IE ، كانت أصلية فقط. ولكن هناك ملفات متعددة لا تعتمد أساسًا على setTimeout ، ولكن على إنشاء قناة postMessages والاشتراك فيها. يعمل هذا بشكل أسرع عمومًا لأن المتصفحات تعطيه الأولوية.
حسنًا ، يتم تنفيذ هذه المهام. في أي نقطة ننهي مهمتنا ونفهم أنه يمكننا اتخاذ الخطوة التالية أو التي يمكننا تقديمها؟
المكدس
المكدس هو بنية بيانات بسيطة تعمل على مبدأ "آخر مرة - أول مخرج" ، أي "أضع الأخير - تحصل على الأول
. " إن أقرب ورقة نظير حقيقية هي مجموعة أوراق اللعب. لذلك ، يحب جدنا إيفان تولوب لعب الورق.

المثال أعلاه ، حيث يوجد بعض الكود ، يمكن أن يكون نفس المثال مطعون في
العرض التقديمي . في مكان ما نسميه handleClick ، وأدخل console.log ، و show showPopup و window. تأكيد. لنقم بتشكيل كومة.
- لذا ، أولاً نأخذ مقبض انقر واضغط على هذه الوظيفة على المكدس - رائع!
- ثم نذهب إلى جسده وننفذه.
- نضع برنامج console.log على المكدس وننفذه على الفور ، لأن كل شيء موجود لتنفيذه.
- بعد ذلك نضع showConfirm - هذا استدعاء دالة - رائع.
- نضع الوظائف على المكدس - نضع جسمها ، أي window.confirm.
ليس لدينا شيء آخر - نحن نقوم بذلك. ستظهر نافذة منبثقة: "هل أنت متأكد؟" ، انقر فوق "نعم" ، وسيترك كل شيء المكدس. الآن انتهينا من عرض showConfirm والجسم clickClick. تم مسح مكدسنا ويمكننا الانتقال إلى المهمة التالية. سؤال: حسنًا ، أعلم الآن أنك بحاجة إلى تقسيمها إلى قطع صغيرة. كيف يمكنني ، على سبيل المثال ، القيام بذلك في معظم الحالات الأولية؟
تقسيم صفيف إلى قطع ومعالجتها بشكل غير متزامن
دعونا نلقي نظرة على المثال الأكثر "الجبين". أحذرك على الفور: من فضلك لا تحاول تكرار ذلك في المنزل - لن يتم تجميعه.

لدينا مصفوفة كبيرة وكبيرة ، ونريد حساب شيء بناءً عليها ، على سبيل المثال ، لتحليل بعض البيانات الثنائية. يمكننا ببساطة تقسيمها إلى أجزاء: معالجة هذه القطعة ، وهذا وذاك. نختار حجم القطعة ، على سبيل المثال ، 10 آلاف عنصر ، ونأخذ في الاعتبار عدد القطع التي سنحصل عليها. لدينا وظيفة parseData التي تدخل في وحدة المعالجة المركزية ويمكنها فعل شيء ثقيل. ثم نقوم بتقسيم الصفيف إلى أجزاء ، وقم بتعيين setTimeout (() => parseData (شريحة) ، 0).
في هذه الحالة ، سيتمكن المتصفح مرة أخرى من إعطاء الأولوية لتفاعل المستخدم والعرض بينهما. أي أنك تقوم على الأقل بإطلاق حلقة الأحداث الخاصة بك ، وتستمر في العمل. يستمر قلبك في النبض ، وهذا أمر جيد.
ولكن هذا هو حقا مثال "الجبين". هناك العديد من واجهات برمجة التطبيقات في المتصفحات لمساعدتك على القيام بذلك بطريقة أكثر تخصصًا.
بالإضافة إلى setTimeout و setInterval ، هناك واجهات برمجة تطبيقات تتجاوز الحدود ، مثل ، على سبيل المثال ، requestAnimationFrame و requestIdleCallback.
من المحتمل أن العديد منهم على دراية
بإطار requestAnimationFrame ، بل ويستخدمونه بالفعل. يتم تنفيذه قبل التقديم. سحرها هو ، أولاً ، أنها تحاول تنفيذ كل 60 إطارًا في الثانية (أو 30 إطارًا في الثانية) ، وثانيًا ، يتم كل هذا مباشرة قبل إنشاء نموذج كائن CSS ، إلخ.

لذلك ، حتى إذا كان لديك العديد من requestAnimationFrame ، فسيقومون بالفعل بتجميع جميع التغييرات ، وسيظهر الإطار كاملاً. في حالة setTimeout ، لا يمكنك بالتأكيد الحصول على هذا الضمان. مجموعة واحدة سيغيّر Timeout شيئًا ، والآخر آخر ، وبين العرض قد ينزلق - سيكون لديك اهتزاز في الشاشة أو شيء آخر. RequestAnimationFrame عظيم لهذا.
بالإضافة إلى ذلك ، هناك أيضًا
requestIdleCallback. ربما سمعت أنه يستخدم في React v16.0 (Fiber). يعمل RequestIdleCallback بطريقة تجعله إذا أدرك المتصفح أن لديه وقتًا بين الإطارات (60 إطارًا في الثانية) للقيام بشيء مفيد ، وفي الوقت نفسه قاموا بكل شيء بالفعل - قاموا بالمهمة ، فعلوا RequestAnimationFrame - يبدو الأمر رائعًا ، ثم يمكن أن تنتج كمية صغيرة ، على سبيل المثال ، 50 مللي ثانية لكل منها ، حتى تتمكن من القيام بشيء (وضع IDLE).
ليس في الرسم البياني أعلاه ، لأنه غير موجود في أي مكان معين. قد يقرر المتصفح وضعه قبل الإطار ، بعد الإطار ، بين requestAnimationFrame والعرض ، بعد المهمة ، قبل المهمة. لا أحد يستطيع أن يضمن هذا.
نضمن لك أنه إذا كان لديك عمل لا علاقة له بتغيير DOM (لأن ثم requestAnimationFrame هو رسم متحرك وما إلى ذلك) ، في حين أنه ليس أولوية فائقة ، ولكنه ملموس ، فإن requestIdleCallback هو طريقك للخروج.
لذا ، إذا كان لدينا عملية طويلة مرتبطة بوحدة المعالجة المركزية ، فيمكننا محاولة تقسيمها إلى أجزاء.
- إذا كان هذا تغيير DOM ، فاستخدم requestAnimationFrame.
- إذا كانت هذه مهمة غير ذات أولوية وقصيرة الأجل وليست صعبة ولن تؤدي إلى زيادة التحميل على وحدة المعالجة المركزية ، ثم requestIdleCallback.
- إذا كانت لدينا مهمة كبيرة قوية يجب تنفيذها باستمرار ، فإننا نتجاوز حلقة الحدث ونستخدم WebWorkers. لا توجد طريقة أخرى.
المهام في المتصفحات:- سحق كل شيء في مهام صغيرة.
- هناك العديد من أنواع المهام.
- يتم تحديد المهام حسب الأولوية من خلال هذه الأنواع من خلال قوائم انتظار المواصفات.
- تقرر المتصفحات الكثير ، والطريقة الوحيدة لفهم كيفية عملها هي ببساطة التحقق مما إذا كان رمز واحد أو آخر قيد التشغيل.
- لكن المواصفات لا تحترم دائمًا!
تكمن المشكلة في أن Ivan Tulup هو جد قديم ، لأن تطبيقات Event Loop في المتصفحات قديمة جدًا أيضًا. لقد تم إنشاؤها قبل كتابة المواصفات ، لذا فإن المواصفات ، للأسف ، يتم احترامها بقدر ما. حتى إذا قرأت ما يجب أن تكون عليه المواصفات ، فلا أحد يضمن أن جميع المتصفحات تدعمها. لذا تأكد من التحقق من المتصفحات لمعرفة كيفية عمل ذلك بالفعل.
جد إيفان تولوب في المتصفحات هو شخص لا يمكن التنبؤ به بشكل سيئ ، مع بعض الميزات المثيرة للاهتمام ، تحتاج إلى تذكر ذلك.
المنهي سانتا: حلقة التميمة في Node.js
Node.js أشبه بشخص من هذا القبيل.

لأنه من ناحية هو نفس الجد بلحية ، ولكن في نفس الوقت يتم توزيع كل شيء على مراحل ويتم رسمه بوضوح حيث يتم القيام به.
مراحل حلقة الأحداث في Node.js:- الموقتات
- رد اتصال معلق ؛
- خمول ، الاستعداد ؛
- استطلاع ؛
- تحقق ؛
- عمليات رد الاتصال قريبة.
كل شيء باستثناء الأخير ليس واضحًا تمامًا ما يعنيه. تحتوي المراحل على مثل هذه الأسماء الغريبة ، لأنه تحت غطاء المحرك ، كما نعلم بالفعل ، لدينا Libuv من أجل حكم الجميع:
- Linux - epoll / POSIX AIO ؛
- BSD - kqueue
- Windows - IOCP ؛
- سولاريس - منافذ الأحداث.
الآلاف منهم جميعا!
بالإضافة إلى ذلك ، يوفر Libuv أيضًا نفس حلقة الأحداث. لا يحتوي على تفاصيل Node.js ، ولكن هناك أطوار ، ويستخدمها Node.js فقط. ولكن لسبب ما أخذت الأسماء من هناك.
دعونا نرى ما تعنيه كل مرحلة بالفعل.
تؤدي مرحلة المؤقتات:
- توقيت استعداد رد الاتصال.
- setTimeout و setInterval ؛
- لكن NOT setImmediate مرحلة مختلفة.
مرحلة رد المكالمات المعلقة
قبل ذلك ، كانت مرحلة التوثيق تسمى استدعاءات I / O. في الآونة الأخيرة ، تم تصحيح هذه الوثائق ، وتوقفت عن التناقض مع نفسها. قبل ذلك ، في مكان ما كتب أنه يتم تنفيذ عمليات رد الاتصال I / O في هذه المرحلة ، في مرحلة أخرى - وهي في مرحلة الاستطلاع. لكن الآن كل شيء مكتوب هناك بشكل لا لبس فيه وبشكل جيد ، لذا اقرأ الوثائق - سيصبح شيء أكثر قابلية للفهم.
في مرحلة رد الاتصال المعلقة ، يتم تنفيذ الاسترجاعات من بعض عمليات النظام (خطأ TCP). أي إذا كان هناك خطأ في مقبس TCP في Unix ، فإنه في هذه الحالة لا يريد التخلص منه على الفور ، ولكن في الاستدعاء ، الذي سيتم تنفيذه في هذه المرحلة. هذا كل ما نحتاج إلى معرفته عنها. عمليا نحن لسنا مهتمين بها.
المرحلة الخاملة ، استعد
في هذه المرحلة ، لا يمكننا فعل أي شيء على الإطلاق ، لذلك سننسى ذلك من حيث المبدأ.

مرحلة الاستطلاع
هذه هي المرحلة الأكثر إثارة للاهتمام في Node.js لأنها تقوم بالعمل المفيد الرئيسي:
- تنفيذ عمليات رد الاتصال بإدخال / إخراج (ليست مرحلة رد اتصال معلقة!).
- في انتظار الأحداث من I / O ؛
- إنه لأمر رائع القيام به على الفور ؛
- لا توقيتات
واستشرافا للمستقبل ، سيتم تنفيذ setImmediate في مرحلة الفحص التالية ، وهي مضمونة قبل الموقتات.
كما تتحكم مرحلة الاستطلاع في تدفق حلقة الحدث. على سبيل المثال ، إذا لم يكن لدينا مؤقتات ، فلن يكون هناك setImmediate ، أي لم يقم أحد بالتوقيت ، ولم يتصل setImmediate ، فسنقوم فقط بحظر هذه المرحلة وننتظر الحدث من I / O ، إذا وصلنا شيء ما ، إذا كان هناك أي استدعاءات إذا سجلنا لشيء ما.
كيف يتم تنفيذ النموذج غير المحظور؟ على سبيل المثال ، في نفس Epoll ، يمكننا الاشتراك في حدث - افتح مقبس وانتظر حتى تتم كتابة شيء إليه. بالإضافة إلى ذلك ، فإن الحجة الثانية هي المهلة ، أي سننتظر Epoll ، ولكن إذا انتهت المهلة ، ولم يأت الحدث من I / O ، فسيتم إنهاء المهلة. إذا وصل إلينا حدث من الشبكة (يكتب شخص ما إلى مقبس) ، فسيأتي.
لذلك ، تسترد مرحلة الاستطلاع أول رد اتصال من الكومة (الكومة هي بنية بيانات تسمح بالتسليم والتسليم مرتبة جيدًا) ، وتستغرق مهلتها ، وتكتب إلى هذه المهلة وتطلق كل شيء. وهكذا ، حتى لو لم يكتب لنا أحد في المقبس ، فإن المهلة ستعمل ، وستعود إلى مرحلة الاستطلاع وسيستمر العمل.
من المهم ملاحظة أنه في مرحلة الاستطلاع يوجد حد لعدد الاستدعاءات في كل مرة.
من المحزن أنه ليس في المراحل المتبقية. إذا قمت بإضافة 10 مليار مهلة ، فأنت تضيف 10 مليار مهلة. لذلك ، فإن المرحلة التالية هي مرحلة الفحص.
تحقق المرحلة
هذا هو المكان الذي يتم فيه تنفيذ setImmediate. المرحلة جميلة في تلك المجموعة ، فوريًا ، يسمى في مرحلة الاستطلاع ، مضمون للتنفيذ في وقت أبكر من المؤقت. لأن المؤقت سيكون فقط على القراد التالي في البداية ، وفي وقت سابق من مرحلة الاستطلاع. لذلك ، لا يمكننا أن نخاف من المنافسة مع أجهزة ضبط الوقت الأخرى واستخدام هذه المرحلة لتلك الأشياء التي لا نريد تنفيذها لسبب ما في رد اتصال.
مرحلة رد المكالمات قريبة
لا تنفذ هذه المرحلة جميع عمليات الاسترداد الخاصة بإغلاق المقبس والأنواع الأخرى:
socket.on('close', …).
لا تنفذها إلا إذا طار هذا الحدث بشكل غير متوقع ، على سبيل المثال ، أرسل شخص ما في الطرف الآخر: "كل شيء - أغلق المقبس - اذهب من هنا ، فاسيا!" ثم ستنجح هذه المرحلة ، لأن الحدث غير متوقع. لكن هذا لا يؤثر علينا بشكل خاص.
معالجة غير متزامنة للقطع في Node.js
ماذا سيحدث إذا وضعنا نفس النمط الذي أخذناه في المتصفحات باستخدام setTimeout على Node.js - أي أننا نقسم الصفيف إلى أجزاء ، لكل قطعة نصنعها setTimeout - 0.
const bigArray = [1..1_000_000] const chunks = getChunks(bigArray) const parseData = (slice) =>
هل تعتقد أن هناك أي مشاكل مع هذا؟
لقد تقدمت بالفعل قليلاً عندما قلت أنه إذا قمت بإضافة 10 آلاف مهلة (أو 10 مليار!) ، سيكون هناك 10 آلاف مؤقت في قائمة الانتظار ، وسيحصل عليها وينفذها - لا توجد حماية من هذا: الحصول على - تنفيذ ، الحصول على - لتحقيق ذلك وهلم جرا إلى ما لا نهاية.
فقط في مرحلة الاستطلاع ، إذا حصلنا باستمرار على حدث من I / O ، في كل مرة يكتب شخص ما شيئًا في المقبس حتى نتمكن من تنفيذ مؤقتات على الأقل و setImmediate ، يكون له حماية محدودة ، ويعتمد على النظام. أي أنها ستختلف على أنظمة التشغيل المختلفة.
لسوء الحظ ، لا تتمتع المراحل الأخرى ، بما في ذلك المؤقتات و setImmediate ،
بمثل هذه الحماية. لذلك ، إذا قمت بذلك كما في المثال ، فسوف يتجمد كل شيء ولن يصل إلى مرحلة الاستطلاع لفترة طويلة جدًا.
ولكن هل تعتقد أن شيئًا ما سيتغير إذا استبدلنا setTimeout (() => parseData (شريحة) ، 0) بـ setImmediate (() => parseData (شريحة))؟ - وبطبيعة الحال ، لا ، لا توجد أيضًا حماية في مرحلة الفحص هناك.
لحل هذه المشكلة ، يمكنك استدعاء
المعالجة العودية .
const parseData = (slice) =>
خلاصة القول هي أننا أخذنا وظيفة parseData وكتبنا نداءها العودي ، ولكن ليس فقط أنفسنا ، ولكن من خلال setImmediate. عند استدعاء هذا في مرحلة الإعداد الفوري ، فإنه يصل إلى العلامة التالية ، وليس إلى المرحلة الحالية. لذلك ، سيؤدي هذا إلى إطلاق حلقة الأحداث ، وسوف تذهب أبعد من ذلك في دائرة. بمعنى ، لدينا عودية متزامنة AsyncParseData ، حيث نقوم بتمرير فهرس معين ، ونحصل على القطعة حسب هذا الفهرس ، ونحللها - ثم نضع قائمة الانتظار على الفور مع الفهرس التالي. سيصل إلى النقطة التالية ويمكننا معالجة هذا الأمر برمته بشكل متكرر.
صحيح أن المشكلة هي أن هذا لا يزال نوعًا من المهام المرتبطة بوحدة المعالجة المركزية. ربما ستظل تزن بطريقة ما وتستغرق وقتًا في Event Loop. على الأرجح تريد أن يكون Node.js الخاص بك مقيدًا بإدخال / إخراج محض.
لذلك ، من الأفضل استخدام بعض الأشياء الأخرى ، على سبيل المثال ،
معالجة تفرع / تجمع مؤشرات الترابط.نعرف الآن عن Node.js ما يلي:
- كل شيء يتم توزيعه على مراحل - حسنًا ، نحن نعرف ذلك بوضوح ؛
- هناك حماية ضد مرحلة الاستطلاع الطويلة جدًا ، ولكن ليس البقية ؛
- يمكن تطبيق أنماط المعالجة العودية حتى لا يتم حظر حلقة الأحداث ؛
- ولكن من الأفضل استخدام شوكة العملية وتجمع سلسلة العمليات الفرعية
يجب أيضًا أن تكون حذرًا في تجمع سلسلة المحادثات ، لأن Node.js يبدأ الأشياء هناك ، على وجه الخصوص ، حل DNS ، لأنه بالنسبة للينكس ، لسبب ما ، فإن وظيفة حل DNS ليست متزامنة. لذلك ، يجب أن يتم تنفيذه في ThreadPool. لحسن الحظ ، على Windows ، ليس كذلك. ولكن هناك يمكنك قراءة الملفات بشكل غير متزامن. للأسف ، هذا مستحيل في لينكس.
في رأيي ، الحد القياسي هو 4 عمليات في ThreadPool. لذلك ، إذا كنت تفعل شيئًا نشطًا هناك ، فسوف تتنافس مع الجميع - مع fs والآخرين. يمكنك التفكير في زيادة ThreadPool ، ولكن أيضًا بعناية شديدة. لذا اقرأ شيئا عن هذا الموضوع.
Microtask: الدورة الدموية الرئوية
لدينا مهام في Node.js ومهام في المتصفحات. ربما تكون قد سمعت بالفعل عن المهام الدقيقة. دعونا نرى ما هو وكيف تعمل ، ونبدأ بالمتصفحات.
Microtask في المتصفحات
لفهم كيفية عمل المهام الدقيقة ، دعنا ننتقل إلى خوارزمية حلقة الأحداث وفقًا لمعيار whatwg ، أي لننتقل إلى المواصفات ونرى كيف يبدو كل شيء.

عند الترجمة إلى اللغة البشرية ، يبدو الأمر كالتالي:
- خذ المهمة المجانية من خطنا
- نقوم بها
- ننفذ نقطة تفتيش المهام الدقيقة - حسنًا ، ما زلنا لا نعرف ما هو ، لكننا نتذكره.
- نقوم بتحديث العرض (إذا لزم الأمر) ، ونعود إلى المربع الأول.

يتم تنفيذها في المكان الموضح على الرسم التخطيطي ، وفي العديد من الأماكن الأخرى ، والتي سنتعرف عليها قريبًا. أي أن المهمة قد انتهت ، ويتم تنفيذ المهام الدقيقة.
مصادر الميكروتكس
المهم - ليس الوعد نفسه ، أي الوعد. ثم. رد الاتصال الذي تم وضعه في ذلك الحين هو مهمة صغيرة. إذا اتصلت بـ 10 إذن - لديك 10 سيارات صغيرة ، 10 آلاف ثم - 10 آلاف سيارة صغيرة.
- مراقب الطفرة.
- Object.observe ، التي تم إيقافها ولا يحتاجها أحد.
كم عدد الذين يستخدمون مراقب الطفرة؟
أعتقد أن القليل يستخدمون مراقب الطفرات. على الأرجح ، يتم استخدام Promise.then أكثر ، ولهذا السبب سنأخذها في الاعتبار في المثال.
ميزات نقطة تفتيش المهام الدقيقة:- نحن نفعل كل شيء - هذا يعني أننا ننفذ جميع المهام الدقيقة التي لدينا في قائمة الانتظار حتى النهاية. نحن لا نترك أي شيء - نحن فقط نأخذ ونفعل كل شيء ، يجب أن تكون صغيرة ، أليس كذلك؟
- لا يزال بإمكانك إنشاء مهمة صغيرة جديدة في العملية ، وسيتم تنفيذها في نفس نقطة تفتيش المهام الدقيقة.
- ما هو مهم أيضًا - يتم تنفيذها ليس فقط بعد تنفيذ المهمة ، ولكن أيضًا بعد مسح المكدس.
هذه نقطة مثيرة للاهتمام. اتضح أنه من الممكن إنشاء مهام صغيرة جديدة وسنقوم بها جميعًا حتى النهاية. ما الذي يمكن أن يقودنا إليه هذا؟

لدينا قلبان. قمت بتحريك القلب الأول باستخدام الرسوم المتحركة JS ، والثاني مع الرسوم المتحركة CSS. هناك ميزة رائعة أخرى تسمى starveMicrotasks. نسمي Promise.resolve ، ثم نضع نفس الوظيفة في ذلك الوقت.
انظر في
العرض التقديمي ما يحدث إذا قمت باستدعاء هذه الوظيفة.
نعم ، سيتوقف قلب JS ، لأننا نضيف مهمة صغيرة ، ثم نضيف مهمة صغيرة فيها ، ثم نضيف مهمة صغيرة فيها ... وهكذا إلى ما لا نهاية.
أي أن النداء التعاودي للميكروتكس سيعلق كل شيء. ولكن يبدو أن لدي كل شيء غير متزامن! يجب أن تتركها ، دعوت setTimeout هناك. لا! لسوء الحظ ، يجب أن تكون حذراً مع المهام الدقيقة ، لذلك إذا كنت تستخدم مكالمة متكررة بطريقة ما ، فكن حذراً - يمكنك حظر كل شيء.
بالإضافة إلى ذلك ، كما نتذكر ، يتم تنفيذ المهام الدقيقة في نهاية تنظيف المكدس. نتذكر ما هو المكدس. اتضح أنه بمجرد خروجنا من الكود الخاص بنا ، تم تنفيذ رد الاتصال setTimeout - هذا كل شيء - ذهبت المهام الدقيقة إلى هناك. هذا يمكن أن يؤدي إلى آثار جانبية مثيرة للاهتمام.
تأمل
في مثال .

هناك زر وحاوية رمادية تقع فيه. نشترك في النقر على الزر والحاوية. , , , .
2 :
- Promise.resolve;
- .then, console.log('RO')
«FUS», – «DAH!» ( ).
, ? , , «FUS RO DAH!» عظيم! , .

, , . – . , - ?

! .

, .
, , , . ,
.
- — buttonHandleClick, .
- Promise.resolve. . , console.log('RO') . .
- console.log('FUS').
- buttonHandleClick . .
- , (divHandleClick) , «DAH!».
- HandleClick .
, . ?
:
- button.click(). .
- button HandleClick.
- Promise.resolve then. , Promise.resolve .
- console.log «FUS».
- buttonHandleClick , .
(click) , , . divHandleClick , , console.log('DAH!') . , .
, , button.click .
. , , . , , .
: () ( ). - , , stopPropagation. , , , , - , .
, - ( junior-) — «», promise, , then , - . ,
, : , , . . , - .
( 4) , . , , , , - . .
, :, . — , , .
Node.js
Node.js Promise.then process.nextTick. , — . , , , , .
process.nextTick
, process.nextTick, setImmediate? Node.js ?
. createServer, EventEmitter, , listen ( ), .
const createServer = () => { const evEmitter = new EventEmitter() return { listen: port => { evEmitter.emit('listening', port) return evEmitter } } } const server = createServer().listen(8080) server.on('listening', () => console.log('listening'))
, , 8080, listening console.log - .
, , - .
createServer, . listen, , . .
, , . ? process.nextTick: evEmitter.emit('listening', port) process.nextTick(() => evEmitter.emit('listening', port)).
,
process.nextTick , . EventEmitter, . , , API, . process.nextTick, emit , userland . createServer, , listen, listening. — process.nextTick — ! , , .
process.nextTick . , .
, process.nextTick , Promise.then . process.nextTick , — , Event Loop, Node.js. , , .
process.nextTick , ghbvtybnm setImmediate , C++ .. process.nextTick .
Async/await
API — async/await, - . . , async/await Promise, Event Loop . , .
, !Frontend Conf — 4 5 , . , :
تعال ، سيكون من المثير للاهتمام!