EventEmitter in NodeJS

EventEmitter in NodeJS

Il modulo nativo di node per lavorare con gli eventi è il modulo event e la sua classe è EventEmitter

indice

Per importare la classe EventEmitter all'interno della nostra applicazione node mi basterà fare:

const {EventEmitter} = require('events')
const customEmitter = new EventEmitter();

La classe EventEmitter fornisce una serie di metodi per lavorare con gli eventi.
I due metodi principali per emissione e gestione di un evento sono:

  • emit che solleva solo l'evento
  • addListener (tenere in ascolto di un evento e poterlo gestire).
    Un alias di addListener è il metodo on.
customEmitter.on('evento1', function(eventoObj){
    console.log('evento catturato');
    console.log(eventoObj.infoEvento);
});
customEmitter.emit('evento1', {infoEvento: 'Primo Evento' });

Spiegazione:
customEmitter.on('evento che voglio gestire', funzione di gestione dell'evento(){ eseguire codice })
customEmitter.emit('evento che voglio sollevare',{oggetto: oggetto che ci permette di passare più informazioni})

emit e on devono girare su una singola istanza EventEmitter

Affinchè un listener resti in ascolto per un certo evento emit e on devono essere invocati sulla stessa istanza di EventEmitter.

Se utilizzo un'istanza EventEmitter per emettettere un evento e un'altra istanza EventEmitter per gestire lo stesso evento non sarò in grado di catturarlo. Un esempio:

// Esempio non funzionante perchè su istanze diverse
const { EventEmitter } = require('events')
const customEmitter1 = new EventEmitter();
const customEmitter2 = new EventEmitter();

customEmitter1.on('meteo', eventoObj => {
    console.log('Evento meteo catturato');
});
customEmitter2.emit('meteo', {condizioni: 'sole',temperatura: 27});

In questo caso, non abbiamo alcun risultato perché abbiamo emesso l'evento meteo sull'istanza customEmitter2  ed abbiamo provato a catturare l'evento su customEmitter1.

Nel caso in cui emetto e gestisco l'evento nello stesso modulo, il problema è facilmente risolvibile, basta usare la stessa istanza EventEmitter.

Nel caso in cui invece devo emettere l'evento in un modulo e gestire l'evento in un altro modulo, le cose si complicano un poco.

Come emettere un evento in un modulo e gestirlo in un altro modulo?

Come detto precedentemente non è possibile emettere l'evento in un istanza e gestirlo in un altro, possiamo definire una classe che vá ad estendere l'evento (quindi lavora sulla stessa istanza).


Per comprenderlo, facciamo un esempio su due moduli meteo.js e app.js.

In questo esempio proveremo ad emettere l'evento in meteo.js ed a gestire l'evento in app.js

meteo.js

const { EventEmitter } = require('events')
const customEmitter = new EventEmitter();

function getMeteo(){
    //emettiamo l'evento meteo all'interno dell'app meteo.js
    customEmitter.emit('meteo', {condizioni: 'sole', temperatura: 28});
};

// esportiamo la funzione getMeteo
module.exports = getMeteo;

app.js

//importiamo la funzione getMeteo
const getMeteo = require('./meteo');
const { EventEmitter } = require('events');
const customEmitter = new EventEmitter();

customEmitter.on('meteo', eventoObj => {
    console.log('Evento meteo catturato');
});
// invochiamo la funzione getMeteo che solleva l'evento meteo (all'interno del modulo meteo.js)
getMeteo();

Risultato:

EventEmitter evento non catturato
Come possiamo vedere l'evento meteo non è stato catturato.
Questo perché è stato emesso l'evento su un istanza EventEmitter (in meteo.js) e gestito su un altra istanza EventEmitter (in app.js)

Come risolvere questo problema?

Per risolvere questo problema definiremo una classe che andrà ad estendere la classe EventMitter in modo che possano lavorare sulla stessa istanza.

meteo.js

const { EventEmitter } = require('events')
const customEmitter = new EventEmitter();

// definiamo la classe Meteo che estende la classe EventEmitter 
class Meteo extends EventEmitter {
 // importiamo la funzione getMeteo all'interno della classe Meteo 
 // (la keyword function all'interno delle classi non serve)
 getMeteo() {
 // anzichè sollevare l'evento con l'istanza customEmitter, lo facciamo con la keyword this.
 this.emit('meteo', {condizioni: 'sole', temperatura: 28});
 }
};


// esportiamo la classe Meteo
module.exports = Meteo;

app.js

//importiamo la funzione getMeteo
const Meteo = require('./meteo');
//creiamo un istanza della classe Meteo
const meteoObj = new Meteo();


//solleviamo l'evento Meteo sull'oggetto meteoObj
meteoObj.on('meteo', eventoObj => {
    console.log('Evento meteo catturato');
});
// invochiamo il metodo getMeteo sull'oggetto meteoObj
meteoObj.getMeteo();

Risultato:

EventEmitter evento catturato con Classe che estende
Come possiamo vedere l'evento meteo è stato catturato.
Questo perché è stato emesso l'evento su un istanza EventEmitter (in meteo.js) e gestito sulla stessa istanza EventEmitter (in app.js) grazie alla classe Meteo (all'interno di meteo.js) che ha permesso l'estensione dell'istanza.

Gestire il codice asincrono attraverso gli eventi

Simuliamo il recupero dell'oggetto {condizioni: 'sole', temperatura: 28} da un API esterna

meteo.js

const { EventEmitter } = require('events')
const customEmitter = new EventEmitter();

// definiamo la classe Meteo che estende la classe EventEmitter 
class Meteo extends EventEmitter {
    // importiamo la funzione getMeteo all'interno della classe Meteo (la keyword function all'interno delle classi non serve)
    // la keyword await la possiamo utilizzare solo con con le funzioni asincrone, scriviamo async davanti alla funzione getMeteo
    async getMeteo() {
        // Simulazione la richiesta ad un API esterna per ottenere le condizioni meteo 
        // La simulazione la facciamo con una promise, passando in input l'oggetto
        const meteoInfo = await Promise.resolve({condizioni: 'sole', temperatura: 28});
        // andiamo a sollevare l'evento meteo, passando come secondo parametro l'oggetto meteoInfo
        this.emit('meteo', meteoInfo );
    }
};


// esportiamo la classe Meteo
module.exports = Meteo;

app.js

//importiamo la funzione getMeteo
const Meteo = require('./meteo');
//creiamo un istanza della classe Meteo
const meteoObj = new Meteo();


//solleviamo l'evento Meteo sull'oggetto meteoObj
meteoObj.on('meteo', eventoObj => {
    console.log('Evento meteo catturato');
    console.log(`la temperatura è di ${eventoObj.temperatura} e fuori c'è il ${eventoObj.condizioni}`);
});

// invochiamo il metodo getMeteo sull'oggetto meteoObj
meteoObj.getMeteo();

Risultato:

EventEmitter simulazione API esterna per gestione eventi asincroni
Come possiamo vedere l'evento meteo è stato correttamente gestito in modo asincrono

Listener Multipli per un singolo evento

E' possibile definire listener multipli per un solo evento, vediamo come:

esempio.js

const evento = require('events');
const customEmitter = new evento.EventEmitter();

customEmitter.on('eventoX', function(){
    console.log('catturato dal primo listener')
});
customEmitter.on('eventoX', function(){
    console.log('catturato dal secondo listener')
});
customEmitter.emit('eventoX');

Risultato:

Come possiamo vedere l'evento eventoX è stato catturato in sequenza dal primo listener e successivamente dal secondo listener.

Metodi di EventEmitter

Oltre a emit e on (alias di addListener) , EventEmitter fornisce anche altri metodi utili, vediamo i più usati:

  • once : Listener "usa e getta", cioè che viene rimosso dopo aver gestito l'evento la prima volta
  • removeListener : Da usare se si vuole eliminare un listener in quel determinato punto

listener.js

const evento = require('events');
const customEmitter = new evento.EventEmitter();

// definiamo una callback esterna
function handlerFn(){
    console.log('evento catturato')
};
// definiamo una callback esterna
function handlerFn2(){
    console.log('evento catturato da handlerFn2')
};

// once: Listener che viene rimosso dopo aver gestito l'evento la prima volta
customEmitter.once('evento',handlerFn);
customEmitter.on('evento',handlerFn2);


customEmitter.emit('evento');
customEmitter.emit('evento');
customEmitter.emit('evento');
// removeListener: per la Rimozione del Listener 
customEmitter.removeListener('evento',handlerFn2);
customEmitter.emit('evento');

Risultato:

EventEmitter eventi catturati con once e rimossi con removeListener
Come possiamo vedere l'evento con once viene gestito una sola volta, mentre l'evento gestito con on viene ripetuto per ogni emit finchè non viene rimosso con removeListener, infatti come si può notare sono 3 gli eventi catturati e non 4.

Scritto da Donato Pirolo

Ciao, sono Donato, frontend developer con una smisurata passione per la SEO. Creo strumenti ad hoc per aiutare aziende e professionisti ad essere cercati sul web e trovare clienti.

Potrebbero interessarti

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Copyright © 2022
linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram