كيفية ترميز باعث الحدث الخاص بك في Node.js: دليل تفصيلي

فهم العناصر الداخلية للعقدة من خلال ترميز الحزم / الوحدات الصغيرة

إذا كنت جديدًا على Node.js ، فهناك العديد من البرامج التعليمية هنا على Medium وفي أماكن أخرى. يمكنك التحقق من مقالتي All About Core Node.JS ، على سبيل المثال.

ولكن دون مزيد من اللغط ، دعنا ننتقل إلى الموضوع قيد المناقشة: "بواعث الأحداث". تلعب بواعث الأحداث دورًا مهمًا جدًا في نظام Node.js البيئي.

EventEmitter عبارة عن وحدة نمطية تسهل الاتصال / التفاعل بين الكائنات في العقدة. EventEmitter هو جوهر بنية Node غير المتزامنة المدفوعة بالحدث. ترث العديد من الوحدات المدمجة في Node من EventEmitter بما في ذلك الأطر البارزة مثل Express.js.

المفهوم بسيط للغاية: كائنات الباعث تصدر أحداثًا مسماة تتسبب في استدعاء المستمعين المسجلين مسبقًا. لذلك ، فإن الكائن الباعث له سمتان رئيسيتان:

  • انبعاث أحداث الاسم.
  • تسجيل وإلغاء تسجيل وظائف المستمع.

إنه يشبه نمط تصميم حانة / فرعي أو مراقب (وإن لم يكن بالضبط).

ما سنقوم ببنائه في هذا البرنامج التعليمي

  • فئة EventEmitter
  • on / addEventListener الأسلوب
  • طريقة إيقاف / removeEventListener
  • طريقة واحدة
  • طريقة الانبعاث
  • طريقة RawListeners
  • طريقة listenerCount

الميزات الأساسية المذكورة أعلاه كافية لتنفيذ نظام كامل باستخدام نموذج الحدث.

قبل الدخول في البرمجة ، دعنا نلقي نظرة على كيفية استخدام فئة EventEmitter. يرجى ملاحظة أن الكود الخاص بنا سيحاكي API الدقيق لوحدة أحداث Node.js.

في الواقع ، إذا استبدلت EventEmitter بوحدة "أحداث" المضمنة في Node.js ، فستحصل على نفس النتيجة.

مثال 1 - قم بإنشاء مثيل باعث حدث وقم بتسجيل بضع نداءات

const myEmitter = new EventEmitter(); function c1() { console.log('an event occurred!'); } function c2() { console.log('yet another event occurred!'); } myEmitter.on('eventOne', c1); // Register for eventOne myEmitter.on('eventOne', c2); // Register for eventOne

عند إصدار الحدث "eventOne" ، يجب استدعاء كل من نداءات الاسترجاعات أعلاه.

myEmitter.emit('eventOne');

سيكون الإخراج في وحدة التحكم كما يلي:

an event occurred! yet another event occurred!

مثال 2 - التسجيل في الحدث ليتم إطلاقه مرة واحدة فقط باستخدام مرة واحدة.

myEmitter.once('eventOnce', () => console.log('eventOnce once fired')); 

انبعاث الحدث "eventOnce":

myEmitter.emit('eventOne');

يجب أن يظهر الإخراج التالي في وحدة التحكم:

eventOnce once fired

أحداث الانبعاث المسجلة مرة أخرى لن يكون لها أي تأثير.

myEmitter.emit('eventOne');

نظرًا لأن الحدث تم إصداره مرة واحدة فقط ، فلن يكون للبيان أعلاه أي تأثير.

مثال 3 - التسجيل للحدث باستخدام معلمات رد الاتصال

myEmitter.on('status', (code, msg)=> console.log(`Got ${code} and ${msg}`));

انبعاث الحدث بالمعلمات:

myEmitter.emit('status', 200, 'ok');

سيكون الإخراج في وحدة التحكم على النحو التالي:

Got 200 and ok

ملاحظة: يمكنك إرسال الأحداث عدة مرات (باستثناء تلك المسجلة بطريقة مرة واحدة).

مثال 4 - إلغاء تسجيل الأحداث

myEmitter.off('eventOne', c1);

الآن إذا قمت بإرسال الحدث على النحو التالي ، فلن يحدث شيء وستكون مشكلة:

myEmitter.emit('eventOne'); // noop

المثال 5 - الحصول على عدد المستمعين

console.log(myEmitter.listenerCount('eventOne'));

ملاحظة: إذا تم إلغاء تسجيل الحدث باستخدام طريقة off أو removeListener ، فسيكون العدد صفرًا.

مثال 6 - الحصول على مستمعين أوليين

console.log(myEmitter.rawListeners('eventOne'));

مثال 7 - مثال تجريبي غير متزامن

// Example 2->Adapted and thanks to Sameer Buna class WithTime extends EventEmitter { execute(asyncFunc, ...args) { this.emit('begin'); console.time('execute'); this.on('data', (data)=> console.log('got data ', data)); asyncFunc(...args, (err, data) => { if (err) { return this.emit('error', err); } this.emit('data', data); console.timeEnd('execute'); this.emit('end'); }); } }

استخدام باعث حدث withTime:

const withTime = new WithTime(); withTime.on('begin', () => console.log('About to execute')); withTime.on('end', () => console.log('Done with execute')); const readFile = (url, cb) => { fetch(url) .then((resp) => resp.json()) // Transform the data into json .then(function(data) { cb(null, data); }); } withTime.execute(readFile, '//jsonplaceholder.typicode.com/posts/1');

تحقق من الإخراج في وحدة التحكم. سيتم عرض قائمة المشاركات مع السجلات الأخرى.

نمط المراقب لباعث الأحداث لدينا

رسم تخطيطي مرئي 1 (الطرق في حدثنا المرسل)

نظرًا لأننا نفهم الآن واجهة برمجة تطبيقات الاستخدام ، فلنبدأ في ترميز الوحدة.

الكود المعياري الكامل لفئة EventEmitter

سنقوم بملء التفاصيل بشكل متزايد في الأقسام التالية.

class EventEmitter { listeners = {}; // key-value pair addListener(eventName, fn) {} on(eventName, fn) {} removeListener(eventName, fn) {} off(eventName, fn) {} once(eventName, fn) {} emit(eventName, ...args) { } listenerCount(eventName) {} rawListeners(eventName) {} }

We begin by creating the template for the EventEmitter class along with a hash to store the listeners. The listeners will be stored as a key-value pair. The value could be an array (since for the same event we allow multiple listeners to be registered).

1. The addListener() method

Let us now implement the addListener method. It takes in an event name and a callback function to be executed.

 addListener(event, fn)  []; this.listeners[event].push(fn); return this; 

A little explanation:

The addListener event checks if the event is already registered. If yes, returns the array, otherwise empty array.

this.listeners[event] // will return array of events or undefined (first time registration)

For example…

Let’s understand this with a usage example. Let’s create a new eventEmitter and register a ‘test-event’. This is the first time the ‘test-event’ is being registered.

const eventEmitter = new EventEmitter(); eventEmitter.addListener('test-event', ()=> { console.log ("test one") } );

Inside addListener () method:

this.listeners[event] => this.listeners['test-event'] => undefined || [] => []

The result will be:

this.listeners['test-event'] = []; // empty array

and then the ‘fn’ will be pushed to this array as shown below:

this.listeners['test-event'].push(fn);

I hope this makes the ‘addListener’ method very clear to decipher and understand.

A note: Multiple callbacks can be registered against that same event.

2. The on method

This is just an alias to the ‘addListener’ method. We will be using the ‘on’ method more than the ‘addListener’ method for the sake of convenience.

on(event, fn) { return this.addListener(event, fn); }

3. The removeListener(event, fn) method

The removeListener method takes an eventName and the callback as the parameters. It removes said listener from the event array.

NOTE: If the event has multiple listeners then other listeners will not be impacted.

First, let’s take a look at the full code for removeListener.

removeListener (event, fn) { let lis = this.listeners[event]; if (!lis) return this; for(let i = lis.length; i > 0; i--) { if (lis[i] === fn) { lis.splice(i,1); break; } } return this; }

Here’s the removeListener method explained step-by-step:

  • Grab the array of listeners by ‘event’
  • If none found return ‘this’ for chaining.
  • If found, loop through all listeners. If the current listener matches with the ‘fn’ parameter use the splice method of the array to remove it. Break from the loop.
  • Return ‘this’ to continue chaining.

4. The off(event, fn) method

This is just an alias to the ‘removeListener’ method. We will be using the ‘on’ method more than the ‘addListener’ method for sake of convenience.

 off(event, fn) { return this.removeListener(event, fn); }

5. The once(eventName, fn) method

Adds a one-timelistener function for the event named eventName. The next time eventName is triggered, this listener is removed and then invoked.

Use for setup/init kind of events.

Let’s take a peek at the code.

once(eventName, fn) { this.listeners[event] = this.listeners[eventName] || []; const onceWrapper = () => { fn(); this.off(eventName, onceWrapper); } this.listeners[eventName].push(onceWrapper); return this; }

Here’s the once method explained step-by-step:

  • Get the event array object. Empty array if the first time.
  • Create a wrapper function called onceWrapper which will invoke the fn when the event is emitted and also removes the listener.
  • Add the wrapped function to the array.
  • Return ‘this’ for chaining.

6. The emit (eventName, ..args) method

Synchronously calls each of the listeners registered for the event named eventName, in the order they were registered, passing the supplied arguments to each.

Returns true if the event had listeners, false otherwise.

emit(eventName, ...args) { let fns = this.listeners[eventName]; if (!fns) return false; fns.forEach((f) => { f(...args); }); return true; }

Here’s the emit method explained step-by-step:

  • Get the functions for said eventName parameter
  • If no listeners, return false
  • For all function listeners, invoke the function with the arguments
  • Return true when done

7. The listenerCount (eventName) method

Returns the number of listeners listening to the event named eventName.

Here’s the source code:

listenerCount(eventName) 

Here’s the listenerCount method explained step-by-step:

  • Get the functions/listeners under consideration or an empty array if none.
  • Return the length.

8. The rawListeners(eventName) method

Returns a copy of the array of listeners for the event named eventName, including any wrappers (such as those created by .once()). The once wrappers in this implementation will not be available if the event has been emitted once.

rawListeners(event) { return this.listeners[event]; }

The full source code for reference:

class EventEmitter { listeners = {} addListener(eventName, fn)  on(eventName, fn) { return this.addListener(eventName, fn); } once(eventName, fn) { this.listeners[eventName] = this.listeners[eventName] || []; const onceWrapper = () => { fn(); this.off(eventName, onceWrapper); } this.listeners[eventName].push(onceWrapper); return this; } off(eventName, fn) { return this.removeListener(eventName, fn); } removeListener (eventName, fn) { let lis = this.listeners[eventName]; if (!lis) return this; for(let i = lis.length; i > 0; i--) { if (lis[i] === fn) { lis.splice(i,1); break; } } return this; } emit(eventName, ...args) { let fns = this.listeners[eventName]; if (!fns) return false; fns.forEach((f) => { f(...args); }); return true; } listenerCount(eventName)  rawListeners(eventName) { return this.listeners[eventName]; } }

The complete code is available here:

//jsbin.com/gibofab/edit?js,console,output

As an exercise feel free to implement other events’ APIs from the documentation //nodejs.org/api/events.html.

If you liked this article and want to see more of similar articles, feel free to give a couple of claps :)

ملاحظة : تم تحسين الكود لسهولة القراءة وليس للأداء. ربما كتمرين ، يمكنك تحسين الكود ومشاركته في قسم التعليقات. لم يتم اختبارها بالكامل لحالات الحافة وقد يتم إيقاف بعض عمليات التحقق لأن هذه كانت عملية كتابة سريعة.

هذه المقالة جزء من دورة الفيديو القادمة "فئة Node.JS الرئيسية - أنشئ إطار عمل MVC الخاص بك ExpressJS-Like MVC من البداية".

لم يتم الانتهاء من عنوان الدورة.