EventEmitter in NodeJS
Il modulo nativo di node per lavorare con gli eventi è il modulo event e la sua classe è EventEmitter
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:
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:
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:
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:
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: