Node.js – Esempi di eventi ed EventEmitter
Una delle ragione dell’alta velocità di Node.js è l’esser basato su eventi. Con Node si avvia un server, istanziando le variabili necessarie, dichiarando le funzioni e aspettando che un evento si verifichi.
Node.js già prevede degli eventi interni, molto utili per la gestione dei processi e in generale tutti gli eventi più comuni di basso livello.
Molti dei moduli che emettono degli eventi si basano sulla classe di Node chiamata EventEmitter.
Se non siete completamente nuovi a Node, già dovreste essere a conoscenza dei più comuni eventi implementati dalle librerie più famose, on('end', ...)
oppure on('error', ...)
. Praticamente tutte le librerie event-based di Node si basano sulla classe EventEmitter!
Quel che forse non sapevate è che anche voi potete sfruttare questa classe e le sue potenzialità, usandola direttamente oppure ereditandola nella vostra classe fatta ad-hoc.
Mostriamo un rapido esempio di come far un uso diretto della classe EventEmitter
:
var EventEmitter = require('events').EventEmitter; var radium = new EventEmitter(); radium.on('radiation', function(ray) { console.log(ray); }); setInterval(function() { radium.emit('radiation', 'GAMMA'); }, 1000); |
Notare come sia realmente facile creare eventi personalizzati, aggiungere degli event listener ed emettere gli eventi passando dei valori. Tutto questo è possibile grazie alla magia di EventEmitter.
Ma ancora potremmo pensare di istanziare più event Listener per un unico evento:
radium.on('radiation', printMessage); radium.on('radiation', sendEmail); radium.on('radiation', computeSomething); |
L’esempio sopra è banale e basato su un’istanza diretta di EventEmitter; come creiamo delle classi che estendano EventEmitter? Node.js ha una libreria chiamata util, che possiede un metodo chiamato inherits, che a sua volta rende possibile estendere facilmente qualsiasi classe con un’altra classe:
var util = require('util'); util.inherits(MyClass, SuperClass); |
Detto questo creiamo un modulo Node.js che estende la classe EventEmitter, facendo attenzione ai commenti presenti nel codice:
// File radio.js var util = require('util'); var EventEmitter = require('events').EventEmitter; // @station - un oggetto con le proprietà `freq` e `name` var Radio = function(station) { // Dobbiamo salvare la referenza di `this` in `self`, così da poter usare il contesto corrente all'interno di setTimeout (o qualsiasi altra funzione di callback) // usando `this` in setTimeout ci riferiamo alla callback in sè, non alla classe Radio. var self = this; // emettiamo l'evento 'open' instantaneamente setTimeout(function() { self.emit('open', station); }, 0); // emettiamo l'evento 'close' dopo 5 secs setTimeout(function() { self.emit('close', station); }, 5000); // EventEmitters eredita un singolo event listener this.on('newListener', function(listener) { console.log('Event Listener: ' + listener); }); }; // estendiamo la classe EventEmitter con la nostra classe Radio util.inherits(Radio, EventEmitter); // specifichiamo che questo modulo è una referenza alla classe Radio module.exports = Radio; |
NB. Se vogliamo aggiungere dei metodi (Radio.prototype.mioMetodo = ...;
) alla nostra classe, è bene farlo PRIMA di chiamare l’istruzione con util.inherits
per estendere la classe.
Ora che abbiamo creato il nostro modulo che estende la classe EventEmitter, vediamo di usarla in un contesto appropriato:
// File index.js var Radio = require('./radio.js'); // L'oggetto stazione var station = { freq: '80.16', name: 'Rock N Roll Radio', }; // crea un'istanza della classe Radio var radio = new Radio(station); // aggiungiamo un event listener per l'evento 'open' radio.on('open', function(station) { console.log('"%s" FM %s OPENED', station.name, station.freq); console.log('♬ ♫♬'); }); // aggiungiamo un event listener per l'evento 'close' radio.on('close', function(station) { console.log('"%s" FM %s CLOSED', station.name, station.freq); }); |
A questo punto possiamo eseguire index.js, et voilà.
Se per qualche ragione non vogliamo usare il modulo util
di Node per estendere la classe, possiamo rifarci ad un’istruzione pura in JS:
Radio.prototype = Object.create(require('events').EventEmitter.prototype); |
Più nel dettaglio
Se vogliamo approfondire ulteriormente la questione EventEmitter. Di fatti ci accorgiamo come la classe EventEmitter possa essere implementata e riassunta in poche righe di codice Javascript:
var EventEmitter = function(){}; EventEmitter.prototype = { on : function(event, fct){ this._events = this._events || {}; this._events[event] = this._events[event] || []; this._events[event].push(fct); }, off : function(event, fct){ this._events = this._events || {}; if( event in this._events === false ) return; this._events[event].splice(this._events[event].indexOf(fct), 1); }, emit : function(event /* , args... */){ this._events = this._events || {}; if( event in this._events === false ) return; for(var i = 0; i < this._events[event].length; i++){ this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)); } } }; |
Chiaramente la classe EventEmitter implementata in Node.js fornisce molti più metodi rispetto a quelli mostrati sopra. Ma gli essenziali rimangono questi 3: on
, off
, emit
.
Conclusioni
EventEmitter permette di scrivere moduli Node.js event-based e dalla sintassi pulita. La classe possiede molti altri metodi utili per sapere quanti listener ci sono ad esempio per un evento e molto altro ancora. Se siete curiosi e volete approfondire rimandiamo alla documentazione ufficiale.
Commenti