de Rajesh Pillai

Cum să vă codificați propriul emițător de evenimente în Node.js: un ghid pas cu pas

Înțelegeți internele nodurilor codificând pachete / module mici

Cum sa va codificati propriul emitator de evenimente in Nodejs
Stăpânirea internelor Node.JS

Dacă sunteți nou în Node.js, există multe tutoriale aici pe Medium și în alte părți. Puteți verifica articolul meu Totul despre Core Node.JS, de exemplu.

Dar, fără alte întrebări, să trecem la subiectul în discuție: „Emițătoare de evenimente”. Emitenții de evenimente joacă un rol foarte important în ecosistemul Node.js.

EventEmitter este un modul care facilitează comunicarea / interacțiunea dintre obiectele din nod. EventEmitter se află în centrul arhitecturii asincrone bazate pe evenimente Node. Multe dintre modulele încorporate ale Node moștenesc de la EventEmitter, inclusiv cadre proeminente precum Express.js.

Conceptul este destul de simplu: obiectele emițătoare emit evenimente numite care determină apelarea ascultătorilor înregistrați anterior. Deci, un obiect emițător are practic două caracteristici principale:

  • Emiterea evenimentelor de nume.
  • Înregistrarea și neînregistrarea funcțiilor ascultătorului.

Este cam ca un model de design pub / sub sau observator (deși nu exact).

ad-banner

Ce vom construi în acest tutorial

  • Clasa EventEmitter
  • pe / addEventListener metoda
  • metoda off / removeEventListener
  • odată metodă
  • emite metoda
  • metoda rawListeners
  • metoda listenerCount

Funcțiile de bază de mai sus sunt suficiente pentru a implementa un sistem complet utilizând modelul de evenimente.

Înainte de a intra în codificare, să aruncăm o privire la modul în care vom folosi clasa EventEmitter. Vă rugăm să rețineți că codul nostru va imita API-ul exact al modulului „evenimente” al Node.js.

De fapt, dacă înlocuiți EventEmitter-ul nostru cu modulul „evenimente” încorporat al Node.js, veți obține același rezultat.

Exemplul 1 – Creați o instanță de emițător de evenimente și înregistrați câteva apeluri de apel

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

Când este emis evenimentul „eventOne”, ar trebui invocate ambele callback-uri de mai sus.

myEmitter.emit('eventOne');

Ieșirea din consolă va fi după cum urmează:

an event occurred!
yet another event occurred!

Exemplul 2— Înregistrarea pentru ca evenimentul să fie concediat o singură dată folosind o singură dată.

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

Emiterea evenimentului „eventOnce”:

myEmitter.emit('eventOne');

Următoarea ieșire ar trebui să apară în consolă:

eventOnce once fired

Emiterea de evenimente înregistrate din nou nu va avea niciun impact.

myEmitter.emit('eventOne');

Deoarece evenimentul a fost emis o singură dată, declarația de mai sus nu va avea niciun impact.

Exemplul 3— Înregistrarea pentru eveniment cu parametri de apel invers

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

Emiterea evenimentului cu parametri:

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

Ieșirea din consolă va fi după cum urmează:

Got 200 and ok

NOTĂ: Puteți emite evenimente de mai multe ori (cu excepția celor înregistrate cu metoda once).

Exemplul 4— Anularea înregistrării evenimentelor

myEmitter.off('eventOne', c1);

Acum, dacă emiteți evenimentul după cum urmează, nu se va întâmpla nimic și va fi un noop:

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

Exemplul 5 – Obținerea numărului de ascultători

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

NOTĂ: Dacă evenimentul a fost neînregistrat folosind metoda off sau removeListener, atunci numărul va fi 0.

Exemplul 6 – Obținerea de ascultători bruti

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

Exemplul 7 – Async Exemplu demo

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

Utilizarea emițătorului de evenimente 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, 'https://jsonplaceholder.typicode.com/posts/1');

Verificați ieșirea din consolă. Lista postărilor va fi afișată împreună cu alte jurnale.

Modelul de observator pentru emițătorul nostru de evenimente

Cum sa va codificati propriul emitator de evenimente in Nodejs

Diagrama vizuală 1 (Metode în EventEmitter)

1611577027 743 Cum sa va codificati propriul emitator de evenimente in Nodejs

Deoarece acum înțelegem API-ul de utilizare, să trecem la codificarea modulului.

Codul complet al cazanului pentru clasa EventEmitter

Vom completa detaliile în mod incremental în următoarele câteva secțiuni.

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) {}
}

Începem prin a crea șablonul pentru clasa EventEmitter împreună cu un hash pentru a stoca ascultătorii. Ascultătorii vor fi stocați ca o pereche cheie-valoare. Valoarea ar putea fi o matrice (deoarece pentru același eveniment permitem înregistrarea mai multor ascultători).

1. Metoda addListener ()

Să implementăm acum metoda addListener. Este nevoie de un nume de eveniment și o funcție de apel invers pentru a fi executate.

  addListener(event, fn) {
    this.listeners[event] = this.listeners[event] || [];
    this.listeners[event].push(fn);
    return this;
  }

O mică explicație:

Evenimentul addListener verifică dacă evenimentul este deja înregistrat. Dacă da, returnează matricea, altfel matrică goală.

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

De exemplu…

Să înțelegem acest lucru cu un exemplu de utilizare. Să creăm un nou eventEmitter și să înregistrăm un „eveniment-test”. Este pentru prima dată când se înregistrează „evenimentul-test”.

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

În interiorul metodei addListener ():

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

Rezultatul va fi:

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

și apoi „fn” va fi împins către această matrice așa cum se arată mai jos:

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

Sper că acest lucru face ca metoda „addListener” să fie foarte clară pentru a descifra și înțelege.

O notă: mai multe apeluri de apel pot fi înregistrate împotriva aceluiași eveniment.

1611577027 229 Cum sa va codificati propriul emitator de evenimente in Nodejs

2. Metoda on

Acesta este doar un alias la metoda „addListener”. Vom folosi metoda „on” mai mult decât metoda „addListener” din motive de comoditate.

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

3. Metoda removeListener (eveniment, fn)

Metoda removeListener ia ca nume parametrii un numeEvent și apelul invers. Elimină ascultătorul din matricea de evenimente.

NOTĂ: Dacă evenimentul are mai mulți ascultători, atunci alți ascultători nu vor fi afectați.

În primul rând, să aruncăm o privire asupra codului complet pentru 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;
}

Iată metoda removeListener explicată pas cu pas:

  • Prindeți gama de ascultători prin „eveniment”
  • Dacă nimeni nu a găsit, întoarceți „aceasta” pentru înlănțuire
  • Dacă este găsit, parcurgeți toți ascultătorii. Dacă ascultătorul curent se potrivește cu parametrul „fn”, utilizați metoda de îmbinare a matricei pentru a o elimina. Pauză din buclă.
  • Întoarceți „acest lucru” pentru a continua înlănțuirea.

4. Metoda off (eveniment, fn)

Acesta este doar un alias la metoda „removeListener”. Vom folosi metoda „on” mai mult decât metoda „addListener” din motive de comoditate.

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

5. Metoda once (eventName, fn)

Adaugă un o data listener funcție pentru evenimentul numit eventName. Data viitoare eventName este declanșat, acest ascultător este eliminat și apoi invocat.

Folosiți pentru evenimente de tip setup / init.

Să aruncăm o privire asupra codului.

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

Iată o singura data metoda explicată pas cu pas:

  • Obțineți obiectul matricei de evenimente. Matrice goală dacă este prima dată.
  • Creați o funcție wrapper numită onceWrapper care va invoca fn atunci când evenimentul este emis și, de asemenea, elimină ascultătorul.
  • Adăugați funcția împachetată în matrice.
  • Întoarceți „acest lucru” pentru înlănțuire.

6. Metoda emit (eventName, ..args)

Apelează sincron fiecare ascultător înregistrat la evenimentul numit eventName, în ordinea în care au fost înregistrate, transmitând argumentele furnizate fiecăruia.

Se intoarce true dacă evenimentul a avut ascultători, false in caz contrar.

emit(eventName, ...args) {
    let fns = this.listeners[eventName];
    if (!fns) return false;
    fns.forEach((f) => {
      f(...args);
    });
    return true;
}
1611577027 750 Cum sa va codificati propriul emitator de evenimente in Nodejs

Iată emite metoda explicată pas cu pas:

  • Obțineți funcțiile pentru respectivul parametru eventName
  • Dacă nu există ascultători, reveniți la fals
  • Pentru toți ascultătorii de funcții, invocați funcția cu argumentele
  • Reveniți la adevărat când ați terminat

7. Metoda listenerCount (eventName)

Returnează numărul de ascultători care ascultă evenimentul numit eventName.

Iată codul sursă:

listenerCount(eventName) {
    let fns = this.listeners[eventName] || [];
    return fns.length;
}

Iată metoda listenerCount explicată pas cu pas:

  • Obțineți funcțiile / ascultătorii în considerare sau o matrice goală, dacă nu există.
  • Întoarceți lungimea.

8. Metoda rawListeners (eventName)

Returnează o copie a matricei de ascultători pentru evenimentul numit eventName, inclusiv orice ambalaje (cum ar fi cele create de .once()). Împachetările de o dată în această implementare nu vor fi disponibile dacă evenimentul a fost emis o singură dată.

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

Codul sursă complet pentru referință:

class EventEmitter {
  listeners = {}
  
  addListener(eventName, fn) {
    this.listeners[eventName] = this.listeners[eventName] || [];
    this.listeners[eventName].push(fn);
    return this;
  }

  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) {
    let fns = this.listeners[eventName] || [];
    return fns.length;
  }

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

Codul complet este disponibil aici:

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

Ca exercițiu, nu ezitați să implementați API-urile altor evenimente din documentație https://nodejs.org/api/events.html.

Dacă v-a plăcut acest articol și doriți să vedeți mai multe articole similare, nu ezitați să dați câteva palme 🙂

NOTĂ: Codul este optimizat pentru lizibilitate și nu pentru performanță. Poate ca exercițiu, puteți optimiza codul și îl puteți partaja în secțiunea de comentarii. Nu am testat complet cazurile marginale și unele validări pot fi dezactivate, deoarece aceasta a fost o scriere rapidă.

Acest articol face parte din cursul video „Node.JS Master Class – Construiește-ți propriul cadru MVC ExpressJS-Like de la zero”.

Titlul cursului nu este încă finalizat.