هذه المقالة عبارة عن بعض الفهم الشخصي لـnodejs في التطوير والتعلم الفعليين، وقد تم تجميعها الآن للرجوع إليها في المستقبل.
: الإدخال/الإخراج، الإدخال والإخراج للنظام.
يمكن فهم النظام كفرد، مثل الشخص. عندما تتحدث، فهو المخرج، وعندما تستمع، فهو الإدخال.
يكمن الاختلاف بين عمليات الإدخال/الإخراج المحظورة والإدخال/الإخراج غير المحظور في ما إذا كان النظام يمكنه استقبال مدخلات أخرى خلال الفترة من الإدخال إلى الإخراج .
فيما يلي مثالان لتوضيح ماهية عمليات الإدخال/الإخراج المحظورة وعمليات الإدخال/الإخراج غير المحظورة:
1. الطهي
أولاً ، نحتاج إلى تحديد نطاق النظام. في هذا المثال، تعتبر عمة المقصف والنادل في المطعم نظامًا.
ثم ما إذا كان يمكنك قبول أوامر الآخرين بين طلب الطعام وتقديمه، يمكنك تحديد ما إذا كان يحظر الإدخال/الإخراج أو لا يحظر الإدخال/الإخراج.
أما بالنسبة لعمة الكافتيريا، فلا يمكنها الطلب للطلاب الآخرين عند الطلب، فقط بعد انتهاء الطالب من الطلب وتقديم الأطباق، يمكنها قبول طلب الطالب التالي، وبالتالي فإن عمة الكافتيريا تمنع الإدخال/الإخراج.
بالنسبة لنادل المطعم، يمكنه خدمة الضيف التالي بعد الطلب وقبل أن يقدم الضيف الطبق، وبالتالي فإن النادل لديه عمليات إدخال/إخراج غير قابلة للحظر.
2. القيام بالأعمال المنزلية
عند غسل الملابس، لا تحتاج إلى انتظار الغسالة. يمكنك مسح الأرض وترتيب المكتب في هذا الوقت. بعد ترتيب المكتب، يتم غسل الملابس ويمكنك تعليق الملابس حتى تجف 25 دقيقة في المجموع.
الغسيل هو في الواقع مدخل/مخرج غير معوق، ويمكنك القيام بأشياء أخرى بين وضع الملابس في الغسالة وإنهاء الغسيل.
السبب وراء قدرة الإدخال/الإخراج غير المحظور على تحسين الأداء هو أنه يمكن أن يوفر الانتظار غير الضروري.
المفتاح لفهم الإدخال/الإخراج غير المحظور هو
/الإخراج غير المحظور لـnodejs؟ كما ذكرنا سابقًا، هناك نقطة مهمة في فهم عمليات الإدخال/الإخراج غير المحظورة وهي تحديد حدود النظام أولاً. حدود النظام للعقدة هي الخيط الرئيسي .
إذا تم تقسيم مخطط البنية أدناه وفقًا لصيانة الخيط، فإن الخط المنقط على اليسار هو خيط العقدة Nodejs، والخط المنقط على اليمين هو خيط C++.
الآن يحتاج مؤشر ترابط Nodejs إلى الاستعلام عن قاعدة البيانات، وهذه عملية إدخال/إخراج نموذجية، ولن تنتظر نتائج الإدخال/الإخراج وتستمر في معالجة العمليات الأخرى المواضيع للحسابات.
انتظر حتى تظهر النتيجة وأعدها إلى مؤشر ترابط Nodejs. قبل الحصول على النتيجة، يمكن لمؤشر ترابط Nodejs أيضًا إجراء عمليات إدخال / إخراج أخرى، لذلك فهو غير محظور.
خيط Nodejs يعادل الجزء الأيسر من النادل، وخيط C ++ هو الشيف.
لذلك، يتم إكمال الإدخال/الإخراج غير المحظور للعقدة عن طريق استدعاء مؤشرات الترابط العاملة لـ C++.
فكيف يتم إخطار مؤشر ترابط العقدة عندما يحصل مؤشر ترابط c++ على النتيجة؟ الجواب هو الحدث .
الحظر: تنام العملية أثناء الإدخال/الإخراج وتنتظر اكتمال الإدخال/الإخراج قبل الانتقال إلى الخطوة التالية
غير المحظورة : تعود الوظيفة فورًا أثناء الإدخال/الإخراج ولا تنتظر العملية الإدخال/الإخراج؛ او لإكمال.
فكيف تعرف النتيجة التي تم إرجاعها، تحتاج إلى استخدام برنامج تشغيل الحدث .
يمكن فهم ما يسمى بالحدث على أنه نفس حدث النقر على الواجهة الأمامية، حيث أكتب أولاً حدث نقرة، لكنني لا أعرف متى سيتم تشغيله، فقط عندما يتم تشغيله، سيتم تشغيل الخيط الرئيسي تنفيذ وظيفة يحركها الحدث.
هذا الوضع هو أيضًا وضع مراقب، أي أنني أستمع إلى الحدث أولاً، ثم أنفذه عند تشغيله.
فكيف يتم تنفيذ محرك الحدث؟ الجواب هو البرمجة غير المتزامنة .
كما ذكرنا سابقًا، تحتوي Nodejs على عدد كبير من عمليات الإدخال/الإخراج غير المحظورة، لذا يجب الحصول على نتائج الإدخال/الإخراج غير المحظورة من خلال وظائف رد الاتصال. هذه الطريقة لاستخدام وظائف رد الاتصال هي برمجة غير متزامنة . على سبيل المثال، تحصل التعليمة البرمجية التالية على النتيجة من خلال وظيفة رد الاتصال:
glob(__dirname+'/**/*', (err, res) => { النتيجة = الدقة console.log ("الحصول على النتيجة") })
المعلمة الأولى لوظيفة رد اتصال Nodejs هي الخطأ، والمعلمات اللاحقة هي النتيجة . لماذا تفعل هذا؟
يحاول { مقابلة (وظيفة () { console.log("ابتسامة") }) } قبض (يخطئ) { console.log("صرخة"، خطأ) } مقابلة الوظيفة (رد الاتصال) { setTimeout(() => { إذا (الرياضيات العشوائية () < 0.1) { رد الاتصال ("النجاح") } آخر { رمي خطأ جديد ("فشل") } }، 500) }
بعد التنفيذ، لم يتم اكتشافه وتم طرح الخطأ عالميًا، مما تسبب في تعطل برنامج Nodejs بأكمله.
لم يتم اكتشافه عن طريق محاولة التقاطه، لأن setTimeout يعيد فتح حلقة الحدث في كل مرة يتم فيها فتح حلقة حدث، يتم إعادة إنشاء سياق مكدس الاستدعاءات. سياق مكدس الاستدعاءات، كل شيء مختلف، لا توجد محاولة التقاط في مكدس الاستدعاءات الجديد هذا، لذلك يتم طرح الخطأ عالميًا ولا يمكن اكتشافه. للحصول على تفاصيل، يرجى الرجوع إلى هذه المقالة المشكلات التي تحدث عند إجراء محاولة الالتقاط في قائمة الانتظار غير المتزامنة.
إذن ماذا تفعل؟ قم بتمرير الخطأ كمعلمة:
مقابلة الوظيفة(رد الاتصال) { setTimeout(() => { إذا (الرياضيات العشوائية () < 0.5) { رد الاتصال ("النجاح") } آخر { رد الاتصال (خطأ جديد ("فشل")) } }، 500) } مقابلة (وظيفة (الدقة) { إذا (خطأ مثيل الدقة) { console.log("صرخة") يعود } console.log("ابتسامة") })
ولكن هذا أكثر إزعاجًا، ويجب عليك إصدار حكم في رد الاتصال، لذلك هناك قاعدة ناضجة. إذا لم تكن موجودة، فهذا يعني أن التنفيذ ناجح.
مقابلة الوظيفة (رد الاتصال) { setTimeout(() => { إذا (الرياضيات العشوائية () < 0.5) { رد الاتصال (فارغة، "النجاح") } آخر { رد الاتصال (خطأ جديد ("فشل")) } }، 500) } مقابلة (وظيفة (الدقة) { إذا (الدقة) { يعود } console.log("ابتسامة") })لن تؤدي طريقة كتابة رد الاتصال لـNodejs
إلى إنشاء منطقة رد الاتصال فحسب، بل ستؤدي أيضًا إلى ظهور مشكلة التحكم في العمليات غير المتزامنة .
يشير التحكم غير المتزامن في العمليات بشكل أساسي إلى كيفية التعامل مع منطق التزامن عند حدوث التزامن. لا تزال تستخدم المثال أعلاه، إذا أجرى زميلك مقابلة مع شركتين، فلن تتم مقابلته من قبل الشركة الثالثة حتى ينجح في مقابلة شركتين، فكيف تكتب هذا المنطق؟ تحتاج إلى إضافة عدد متغير عالميًا:
var count = 0 مقابلة ((يخطئ) => { إذا (يخطئ) { يعود } العد++ إذا (العدد >= 2) { // منطق المعالجة} }) مقابلة ((يخطئ) => { إذا (يخطئ) { يعود } العد++ إذا (العدد >= 2) { // منطق المعالجة} })
الكتابة مثل ما سبق مزعجة وقبيحة للغاية. ولذلك ظهرت فيما بعد أساليب كتابة الوعد والتزامن/الانتظار.
أن حلقة الحدث الحالية لا يمكنها الحصول على النتيجة، لكن حلقة الحدث المستقبلية ستعطيك النتيجة. إنه مشابه جدًا لما قد يقوله الحثالة.
الوعد ليس مجرد حثالة، ولكنه أيضًا آلة حالة:
const pro = new Promise((resolve,reject) => { setTimeout(() => { العزم('2') }، 200) }) console.log(pro) // طباعة: Promise { <pending> }
سيؤدي تنفيذ ثم أو التقاط إلى إرجاع وعد جديد . يتم تحديد الحالة النهائية للوعد من خلال نتائج تنفيذ وظائف رد الاتصال الخاصة بـthen وcatch:
مقابلة الوظيفة () { إرجاع وعد جديد ((حل، رفض) => { setTimeout(() => { إذا (Math.random() > 0.5) { حل ("النجاح") } آخر { رفض (خطأ جديد ("فشل")) } }) }) } وعد فار = مقابلة () فار وعد1 = وعد.ثم(() => { إرجاع وعد جديد ((حل، رفض) => { setTimeout(() => { حل ("قبول") }، 400) }) })
يتم تحديد حالة الوعد 1 من خلال حالة الوعد في المقابل، أي حالة الوعد 1 بعد تنفيذ الوعد في المقابل. ما هي فوائد هذا؟ هذا يحل مشكلة رد الاتصال الجحيم .
وعد فار = مقابلة () .ثم(() => { مقابلة العودة () }) .ثم(() => { مقابلة العودة () }) .ثم(() => { مقابلة العودة () }) .قبض (ه => { console.log(ه) })
ثم إذا تم رفض حالة الوعد المرتجع، فسيتم استدعاء المصيد الأول، ولن يتم استدعاء الوعد اللاحق. تذكر: المكالمات المرفوضة أولًا، ثم حل المكالمات أولًا.
إذا كان الوعد مخصصًا فقط لحل عمليات الاسترجاعات الجحيمية، فهو صغير جدًا بحيث لا يمكن التقليل من أهمية الوعد، وتتمثل الوظيفة الرئيسية للوعد في حل مشكلة التحكم في العمليات غير المتزامنة. إذا كنت ترغب في إجراء مقابلة مع شركتين في نفس الوقت:
function مقابلة() { إرجاع وعد جديد ((حل، رفض) => { setTimeout(() => { إذا (Math.random() > 0.5) { حل ("النجاح") } آخر { رفض (خطأ جديد ("فشل")) } }) }) } يعد .all([مقابلة()، مقابلة()]) .ثم(() => { console.log("ابتسامة") }) // إذا رفضت شركة ما، فاقبض عليها .catch(() => { console.log("صرخة") })
ما هو بالضبط المزامنة/الانتظار:
console.log(async function() { العودة 4 }) console.log(function() { إرجاع وعد جديد ((حل، رفض) => { العزم(4) }) })
النتيجة المطبوعة هي نفسها، أي أن المزامنة/الانتظار هي مجرد سكر نحوي للوعد.
نحن نعلم أن محاولة الالتقاط تلتقط الأخطاء بناءً على مكدس الاستدعاءات ، ويمكنها فقط التقاط الأخطاء الموجودة فوق مكدس الاستدعاءات. ولكن إذا كنت تستخدم الانتظار، فيمكنك اكتشاف الأخطاء في جميع الوظائف في مكدس الاستدعاءات. حتى لو تم طرح الخطأ في مكدس الاستدعاءات لحلقة حدث أخرى، مثل setTimeout.
بعد تحويل رمز المقابلة، يمكنك أن ترى أنه تم تبسيط الكود كثيرًا.
يحاول { في انتظار المقابلة (1) في انتظار المقابلة (2) في انتظار المقابلة (2) } قبض (ه => { console.log(ه) })
ماذا لو كانت مهمة موازية؟
انتظر Promise.all([interview(1), Interview(2)])
نظرًا لعدم حظر I/0 لـNodejs، من الضروري استخدام الأساليب المستندة إلى الحدث للحصول على نتائج الإدخال/الإخراج للحصول على النتائج، يجب عليك استخدام البرمجة غير المتزامنة، مثل وظائف رد الاتصال. فكيف يتم تنفيذ وظائف رد الاتصال هذه للحصول على النتائج؟ ثم تحتاج إلى استخدام حلقة الحدث.
حلقة الحدث هي الأساس الرئيسي لتحقيق وظيفة الإدخال/الإخراج غير المحظورة لـnodejs. تعد عمليات الإدخال/الإخراج غير المحظورة وحلقة الأحداث من الإمكانيات التي توفرها مكتبة C++ libuv
.
عرض الكود:
const eventsloop = { طابور: []، حلقة() { بينما (هذا. قائمة الانتظار. الطول) { رد اتصال const = this.queue.shift() أتصل مرة أخرى() } setTimeout(this.loop.bind(this), 50) }, إضافة (رد الاتصال) { this.queue.push(رد الاتصال) } } حلقة الأحداث.loop() setTimeout(() => { Eventloop.add(() => { console.log('1') }) }، 500) setTimeout(() => { Eventloop.add(() => { console.log('2') }) }, 800)
setTimeout(this.loop.bind(this), 50)
يضمن أنه بعد 50 مللي ثانية، سيتحقق مما إذا كان هناك رد اتصال في قائمة الانتظار، وإذا كان الأمر كذلك، فسيتم تنفيذه. وهذا يشكل حلقة الحدث.
بالطبع، الأحداث الفعلية أكثر تعقيدًا، وهناك أكثر من قائمة انتظار واحدة، على سبيل المثال، هناك قائمة انتظار تشغيل الملفات وقائمة انتظار زمنية.
حلقة أحداث ثابتة = { طابور: []، فسكويو: []، قائمة انتظار المؤقت: []، حلقة() { بينما (هذا. قائمة الانتظار. الطول) { رد اتصال const = this.queue.shift() أتصل مرة أخرى() } this.fsQueue.forEach(رد الاتصال => { إذا (تم) { أتصل مرة أخرى() } }) setTimeout(this.loop.bind(this), 50) }, إضافة (رد الاتصال) { this.queue.push(رد الاتصال) } }
أولاً وقبل كل شيء، نحن نفهم ما هو الإدخال/الإخراج غير المحظور، أي تخطي تنفيذ المهام اللاحقة فورًا عند مواجهة الإدخال/الإخراج، ولن ننتظر نتيجة الإدخال/الإخراج. عند معالجة الإدخال / الإخراج، سيتم استدعاء وظيفة معالجة الأحداث التي قمنا بتسجيلها، والتي تسمى مدفوعة بالحدث. البرمجة غير المتزامنة ضرورية لتنفيذ محرك الأحداث. البرمجة غير المتزامنة هي الرابط الأكثر أهمية في العقدة. فهي تنتقل من وظيفة رد الاتصال إلى الوعد وأخيرًا إلى غير المتزامن/الانتظار (باستخدام طريقة متزامنة لكتابة المنطق غير المتزامن).