Kaip koduoti savo įvykių skleidėją „Node.js“: nuoseklus vadovas

Supraskite mazgo vidinę dalį koduodami mažus paketus / modulius

Jei dar nesinaudojote „Node.js“, čia yra daug mokomųjų programų „Medium“ ir kitur. Pavyzdžiui, galite peržiūrėti mano straipsnį „Viskas apie pagrindinį mazgą“.

Bet be ilgesnio svarstymo pereikime prie aptariamos temos: „Renginių skleidėjai“. Renginių skleidėjai vaidina labai svarbų vaidmenį „Node.js“ ekosistemoje.

„EventEmitter“ yra modulis, palengvinantis mazgų objektų bendravimą / sąveiką. „EventEmitter“ yra „Node“ asinchroninės įvykių valdomos architektūros šerdis. Daugelis „Node“ integruotų modulių yra paveldimi iš „EventEmitter“, įskaitant tokias žinomas sistemas kaip „Express.js“.

Ši koncepcija yra gana paprasta: spinduliuojantys objektai skleidžia įvardintus įvykius, dėl kurių iškviečiami anksčiau registruoti klausytojai. Taigi spinduliuojantis objektas iš esmės turi dvi pagrindines savybes:

  • Vardinių įvykių skleidimas.
  • Klausytojo funkcijų registravimas ir išregistravimas.

Tai panašu į užeigos / poilsio vietos ar stebėtojo dizaino modelį (nors ir ne visai tiksliai).

Ką kursime šioje pamokoje

  • „EventEmitter“ klasė
  • on / addEventListener metodas
  • off / removeEventListener metodas
  • kartą metodas
  • skleisti metodą
  • rawListeners metodas
  • klausytojoSkaitymo metodas

Aukščiau nurodytų pagrindinių funkcijų pakanka, kad būtų įdiegta visa sistema, naudojant įvykių modelį.

Prieš pradėdami naudotis kodavimu, pažvelkime, kaip naudosime „EventEmitter“ klasę. Atminkite, kad mūsų kodas imituos tikslią „Node.js“ įvykių “modulio API.

Tiesą sakant, jei pakeisite „EventEmitter“ į „Node.js“ integruotą „įvykių“ modulį, gausite tą patį rezultatą.

1 pavyzdys - sukurkite įvykių skleidėjo egzempliorių ir užregistruokite keletą atgalinių skambučių

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

Kai įvykis „eventOne“ yra skleidžiamas, reikia pasinaudoti abiem aukščiau nurodytais skambučiais.

myEmitter.emit('eventOne');

Pulto išvestis bus tokia:

an event occurred! yet another event occurred!

2 pavyzdys - registruojantis, kad įvykis būtų paleistas tik vieną kartą, naudojant vieną kartą.

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

Renginio „eventOnce“ paskelbimas:

myEmitter.emit('eventOne');

Pulte turėtų būti toks išvestis:

eventOnce once fired

Dar kartą užregistruotų įvykių skleidimas neturės jokios įtakos.

myEmitter.emit('eventOne');

Kadangi įvykis buvo išleistas tik vieną kartą, minėtas teiginys neturės jokios įtakos.

3 pavyzdys - registracija į įvykį su atgalinio ryšio parametrais

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

Renginio skleidimas su parametrais:

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

Pulto išvestis bus tokia:

Got 200 and ok

PASTABA: įvykius galite skleisti kelis kartus (išskyrus tuos, kurie užregistruoti taikant vieną kartą metodą).

4 pavyzdys - įvykių neregistravimas

myEmitter.off('eventOne', c1);

Jei paskleisite įvykį taip, nieko neįvyks ir tai bus langelis:

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

5 pavyzdys - klausytojų skaičiaus nustatymas

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

PASTABA: Jei įvykis nebuvo išregistruotas naudojant „off“ arba „removeListener“ metodą, skaičius bus 0.

6 pavyzdys - Neapdorotų klausytojų pritraukimas

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

7 pavyzdys - „Async“ pavyzdinė demonstracinė versija

// 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'); }); } }

„EventTimitter“ naudojimas:

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');

Patikrinkite išvestį konsolėje. Pranešimų sąrašas bus rodomas kartu su kitais žurnalais.

Stebėtojo pavyzdys mūsų renginio davėjui

1 vaizdinė diagrama („EventEmitter“ metodai)

Kadangi mes dabar suprantame naudojimo API, pereikime prie modulio kodavimo.

Pilnas „EventEmitter“ klasės katilo kodas

Kitose porose skyrių išsamiai pildysime detales.

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 :)

PASTABA : Kodas yra optimizuotas skaitymui, o ne našumui. Galbūt kaip pratimą galite optimizuoti kodą ir pasidalinti juo komentarų skyriuje. Iki galo neišbandžiau krašto atvejų ir kai kurie patvirtinimai gali būti išjungti, nes tai buvo greitas rašymas.

Šis straipsnis yra būsimo vaizdo kurso „Node.JS Master Class - Build Your Own ExpressJS-like MVC Framework from null“ dalis.

Kurso pavadinimas dar nėra baigtas.