"حلقة الحدث" للعقدة هي جوهر قدرتها على التعامل مع التزامن الكبير والإنتاجية العالية. هذا هو المكان الأكثر سحرية. وفقًا لهذا ، يمكن فهم Node.js بشكل أساسي على أنها "سلسلة واحدة" ، كما أنها تتيح معالجة العمليات التعسفية في الخلفية. ستوضح هذه المقالة كيف تعمل حلقات الأحداث ويمكنك أن تشعر بسحرها أيضًا.
البرمجة التي تعتمد على الحدث
لفهم حلقات الأحداث ، يجب أن نفهم أولاً برمجة محرك الأحداث. ظهر في عام 1960. اليوم ، تستخدم البرمجة التي تعتمد على الحدث على نطاق واسع في برمجة واجهة المستخدم. أحد الاستخدامات الرئيسية لـ JavaScript هو التفاعل مع DOM ، لذلك من الطبيعي استخدام واجهة برمجة تطبيقات قائمة على الأحداث.
ببساطة تحديد: يتحكم البرمجة التي تعتمد على الحدث في عملية التقديم من خلال التغييرات في الأحداث أو الحالات. يتم تنفيذها بشكل عام من خلال مراقبة الأحداث. بمجرد اكتشاف الحدث (على سبيل المثال ، يتغير الحالة) ، يتم استدعاء وظيفة رد الاتصال المقابلة. تبدو مألوفة؟ في الواقع ، هذا هو مبدأ العمل الأساسي لحلقة حدث Node.js.
إذا كنت على دراية بتطوير JavaScript من جانب العميل ، فكر في طرق .on*() ، مثل element.onclick () ، والتي تستخدم للدمج مع عناصر DOM لتمرير تفاعل المستخدم. يتيح وضع العمل هذا تشغيل أحداث متعددة على مثيل واحد. يؤدي Node.js إلى تشغيل هذا الوضع من خلال EventEmitter (مولد الحدث) ، كما هو الحال في المقبس ووحدات "HTTP" على جانب الخادم. قد يتم تشغيل تغييرات واحدة أو أكثر من حالة واحدة.
نمط شائع آخر هو التعبير عن النجاح والفشل. هناك عمومًا طريقتان شائعتان للتنفيذ الآن. أول شيء هو تمرير "استثناء الخطأ" إلى رد الاتصال ، والذي يتم تمريره عمومًا إلى وظيفة رد الاتصال كمعلمة الأولى. النوع الثاني هو استخدام وضع تصميم الوعود ، وتم إضافة ES6. ملاحظة* يستخدم وضع الوعد طريقة كتابة سلسلة وظائف تشبه jQuery لتجنب تعشيش وظيفة رد الاتصال العميق ، مثل:
نسخة الكود كما يلي:
$ .getjson ('/getuser'). تم (SuccessHandler) .fail (FailHandler)
تستخدم وحدات "FS" (نظام الملفات) في الغالب نمط تمرير الاستثناءات في رد الاتصال. من الناحية الفنية ، يؤدي ذلك إلى إجراء مكالمات معينة ، مثل الحدث المرفق FS.ReadFile () ، ولكن يتم استخدام واجهة برمجة التطبيقات فقط لتذكير المستخدم بالتعبير عن النجاح أو فشل العملية. يتم اختيار واجهة برمجة التطبيقات هذه لأسباب معمارية ، وليس القيود الفنية.
هناك اعتقاد خاطئ شائع هو أن بواعث الأحداث غير متزامنة بطبيعتها عند تشغيل الأحداث ، لكن هذا غير صحيح. فيما يلي مقتطف رمز بسيط لإثبات ذلك.
نسخة الكود كما يلي:
وظيفة myemitter () {
eventemitter.call (هذا) ؛
}
util.inherits (myemitter ، eventemitter) ؛
myemitter.prototype.dostuff = دالة dostuff () {
console.log ('قبل')
Emitter.emit ('Fire')
console.log ('بعد')}
} ؛
var me = new myemitter () ؛
me.on ('Fire' ، function () {
console.log ('Emit Fired') ؛
}) ؛
me.dostuff () ؛
// الإخراج:
// قبل
// انبعاث أطلقت
// بعد
ملاحظة* إذا كان Emitter.emit غير متزامن ، يجب أن يكون الإخراج
// قبل
// بعد
// انبعاث أطلقت
غالبًا ما يتجلى EventEmitter غير متزامن لأنه يستخدم غالبًا لإخطار العمليات التي تحتاج إلى إكمالها بشكل غير متزامن ، لكن API Eventemitter نفسها متزامنة تمامًا. يمكن تنفيذ وظيفة الاستماع بشكل غير متزامن ، ولكن يرجى ملاحظة أنه سيتم تنفيذ جميع وظائف الاستماع بشكل متزامن بالترتيب الذي تمت إضافته.
نظرة عامة على الآلية وتجميع الخيوط
العقدة نفسها تعتمد على مكتبات متعددة. واحد منهم هو Libuv ، وهي مكتبة تتعامل بطريقة سحرية لعلاج قوائم وقوائم الأحداث غير المتزامنة.
تستخدم العقدة أكبر عدد ممكن من الوظائف الحالية لاستخدام نواة نظام التشغيل. على سبيل المثال ، يتم إنشاء طلب استجابة ، يتم إعادة توجيه الاتصالات وتفويضها إلى النظام للمعالجة. على سبيل المثال ، يتم وضع الاتصالات الواردة من خلال نظام التشغيل حتى يمكن معالجتها بواسطة العقدة.
ربما تكون قد سمعت أن العقدة تحتوي على تجمع مؤشرات ترابط ، وقد تتساءل: "إذا كانت العقدة تتعامل مع المهام بالترتيب ، فلماذا تحتاج إلى تجمع مؤشر ترابط؟" هذا لأنه في النواة ، لا يتم تنفيذ جميع المهام بشكل غير متزامن. في هذه الحالة ، يجب أن تكون Node.js قادرة على قفل الخيط لفترة من الوقت أثناء تشغيله حتى يتمكن من الاستمرار في تنفيذ حلقة الحدث دون حظره.
ما يلي هو مخطط مثال بسيط لإظهار آلية التشغيل الداخلية:
┌ ┌
─
│ └─
│ ┌┌ ┌
│ │ في انتظار عمليات الاسترداد │
│ └ └ ─ ─ ┘ ┌ ┌
│ ┌ ┌ ─ ┴ ─ │ │ │ │ │ │ │
│ │ │ │◄ connections ، │
│ └ └ - بيانات ، إلخ. │
│ ┌ ┌ ─ ─ ─
─ setimmediate │
└ └
هناك بعض الصعوبات في فهم آلية التشغيل الداخلية لحلقة الحدث:
جميع عمليات الاسترجاع مسبقًا عبر Process.NextTick () قبل نهاية مرحلة واحدة من حلقة الحدث (على سبيل المثال ، مؤقت) والانتقال إلى المرحلة التالية. هذا سيتجنب الدعوة العودية المحتملة للمعالجة. nexttick () ، مما تسبب في حلقة لا حصر لها.
"معلقة عمليات الاسترجاعات" عبارة عن رد اتصال في قائمة انتظار رد الاتصال لن تتم معالجته بواسطة أي دورة حلقة حدث أخرى (على سبيل المثال ، تم تمريرها إلى Fs.write).
باعث الحدث وحلقة الحدث
من خلال إنشاء EventEmitter ، يمكن تبسيط التفاعل مع حلقات الأحداث. إنه تغليف عالمي يسهل عليك إنشاء واجهات برمجة التطبيقات القائمة على الأحداث. كيف يتفاعل الاثنان غالبًا ما يجعل المطورين يشعرون بالارتباك.
يوضح المثال التالي أن نسيان أن الحدث قد تم تشغيله بشكل متزامن قد يتسبب في تفويت الحدث.
نسخة الكود كما يلي:
// بعد V0.10 ، تتطلب ("الأحداث"). لم يعد هناك حاجة إلى EventEmitter
var eventemitter = require ("الأحداث") ؛
var util = require ('Util') ؛
وظيفة mything () {
eventemitter.call (هذا) ؛
dofirstthing () ؛
this.emit ('thing1') ؛
}
util.inherits (mything ، eventemitter) ؛
var mt = new mything () ؛
Mt.on ('thing1' ، وظيفة Onthing1 () {
// آسف ، لن يحدث هذا الحادث أبدًا
}) ؛
لن يتم القبض على حدث "Thing1" أعلاه من قِبل Mything () ، لأنه يجب إنشاء إنشاء Mything () قبل أن يتمكن من الاستماع للأحداث. فيما يلي حل بسيط دون الحاجة إلى إضافة أي إغلاق إضافي:
نسخة الكود كما يلي:
var eventemitter = require ("الأحداث") ؛
var util = require ('Util') ؛
وظيفة mything () {
eventemitter.call (هذا) ؛
dofirstthing () ؛
setImmediate (emitthing1 ، هذا) ؛
}
util.inherits (mything ، eventemitter) ؛
وظيفة emitthing1 (الذات) {
self.emit ('thing1') ؛
}
var mt = new mything () ؛
Mt.on ('thing1' ، وظيفة Onthing1 () {
// ينفذ
}) ؛
يمكن أن يعمل المخطط التالي أيضًا ، ولكن يتم فقدان بعض الأداء:
نسخة الكود كما يلي:
وظيفة mything () {
eventemitter.call (هذا) ؛
dofirstthing () ؛
// باستخدام الدالة#bind () سيفقد الأداء
setImMediate (this.emit.bind (this ، 'thing1')) ؛
}
util.inherits (mything ، eventemitter) ؛
هناك مشكلة أخرى تثير خطأ (استثناء). من الصعب بالفعل معرفة المشكلة في تطبيقك ، ولكن بدون مكدس الاتصال (ملاحظة *E.Stack) ، يكاد تصحيح الأخطاء مستحيلًا. سيتم فقد مكدس المكالمات عند طلب الخطأ من قبل جهاز التحكم عن بُعد بشكل غير متزامن. هناك حلان محتملان: تزامن متزامن أو ضمان تمرير خطأ مع معلومات مهمة أخرى. يوضح المثال التالي هذين الحلين:
نسخة الكود كما يلي:
mything.prototype.foo = دالة foo () {
// سيتم تشغيل هذا الخطأ بشكل غير متزامن
var er = dofirstthing () ؛
إذا (إيه) {
// عند التشغيل ، تحتاج إلى إنشاء خطأ جديد يحتفظ بمعلومات مكدس الاتصال في الموقع.
setImMediate (emiterror ، هذا ، خطأ جديد ('الأشياء السيئة')) ؛
يعود؛
}
// خطأ الزناد ومعالجته على الفور (المزامنة)
var er = dosecondthing () ؛
إذا (إيه) {
this.emit ("خطأ" ، "المزيد من الأشياء السيئة") ؛
يعود؛
}
}
تقييم الوضع. عندما يتم تشغيل خطأ ، من الممكن معالجته على الفور. بدلاً من ذلك ، قد يكون تافهاً ويمكن التعامل معه بسهولة أو معالجته لاحقًا. بالإضافة إلى ذلك ، فإن تمرير خطأ عبر مُنشئ ليس فكرة جيدة ، لأن مثيل الكائن المبني غير مكتمل. الاستثناء هو الحالة التي تم فيها طرح الخطأ مباشرة الآن.
خاتمة
تستكشف هذه المقالة بإيجاز آلية التشغيل الداخلية والتفاصيل الفنية لحلقة الحدث. جميعهم يعتبرون بعناية. ستناقش مقال آخر التفاعل بين حلقات الأحداث ونواة النظام وإظهار سحر Nodejs الذي يعمل بشكل غير متزامن.