# Node.js EventEmitters
EventEmitter (opens new window) is a very important class in Node.js. It provides a channel for events to be dispatched and listeners to be notified. Many objects you’ll encounter in Node.js inherit from EventEmitter, like the Streams class.
# The Observer Pattern
The concept behind EventEmitter is quite simple:
emitter objects emit named events that cause previously registered listeners to be called. So, an emitter object basically has two main features:
- Emitting name events.
- Registering and unregistering listener functions.
It’s kind of like a pub/sub or observer design pattern (though not exactly).
The Observer Pattern
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
See also
- Learning JavaScript Design Patterns. A book by Addy Osmani (opens new window) and the chapter The Observer Pattern (opens new window)
# La Clase EventEmitter
- Algunos métodos de los objetos de la clase EventEmitter (opens new window):
# on
The on (opens new window) method is used to register listeners:
[~/.../p4-t2-networking/networking-with-sockets-chapter-3-crguezl(master)]$ node
Welcome to Node.js v12.10.0.
Type ".help" for more information.
> const {EventEmitter} = require("events")
undefined
> function c1() { console.log('an event occurred!');}
undefined
> function c2() { console.log('yet another event occurred!');}
undefined
> const myEmitter = new EventEmitter();
undefined
> myEmitter.on('eventOne', c1);
EventEmitter {
_events: [Object: null prototype] { eventOne: [Function: c1] },
_eventsCount: 1,
_maxListeners: undefined
}
> myEmitter.on('eventOne', c2)
EventEmitter {
_events: [Object: null prototype] {
eventOne: [ [Function: c1], [Function: c2] ]
},
_eventsCount: 1,
_maxListeners: undefined
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
When you emit the event, all the listeners are called:
> myEmitter.emit('eventOne');
an event occurred!
yet another event occurred!
true
2
3
4
# once
emitter.once(eventName, listener) (opens new window) adds a one-time listener function for the event named eventName
. The next time eventName
is triggered, this listener is removed and then invoked.
> myEmitter.once('eventOnce', () => console.log('eventOnce once fired'));
EventEmitter {
_events: [Object: null prototype] {
eventOne: [ [Function: c1], [Function: c2] ],
eventOnce: [Function: bound onceWrapper] { listener: [Function] }
},
_eventsCount: 2,
_maxListeners: undefined
}
2
3
4
5
6
7
8
9
Since the once
method was called, the listener is called only once:
> myEmitter.emit('eventOnce');
eventOnce once fired
true
> myEmitter.emit('eventOnce');
false
> myEmitter.emit('eventOnce');
false
2
3
4
5
6
7
# Argumentos
You can pass arguments to the listeners of your event:
> myEmitter.on('status', (code, msg)=> console.log(`Got ${code} and ${msg}`));
EventEmitter {
_events: [Object: null prototype] {
eventOne: [ [Function: c1], [Function: c2] ],
status: [Function]
},
_eventsCount: 2,
_maxListeners: undefined
}
2
3
4
5
6
7
8
9
Now you can emit the event with arguments:
> myEmitter.emit('status', 200, 'ok');
Got 200 and ok
2
# off
off
is an alias for emitter.removeListener(eventName, listener) (opens new window).
It removes a listener from the listeners array for the specified event:
> myEmitter.off('eventOne', c1);
EventEmitter {
_events: [Object: null prototype] {
eventOne: [Function: c2],
status: [Function]
},
_eventsCount: 2,
_maxListeners: undefined
}
2
3
4
5
6
7
8
9
Since we removed the listener c1
, it won’t be called when we emit the event:
> myEmitter.emit('eventOne');
yet another event occurred!
true
2
3
# listenerCount and rawListeners
The method listenerCount
returns the number of listeners for a given event:
> myEmitter.listenerCount('eventOne')
1
2
The method rawListeners
returns an array of listeners for a given event:
> myEmitter.rawListeners('eventOne')
[ [Function: c2] ]
2
# Ejercicio
Vamos ahora a escribir una clase WithTime
cuyos objetos disponen de un método execute
que permite ejecutar
una función asíncrona asyncfun
que acepta como último argumento una callback cb
.
Como es habitual, se supone que la callback es llamada cb(err, data)
por asyncfun
cuando esta termina su tarea asíncrona.
El primer parámetro err
indica el error si lo hubo y el segundo data
con el resultado de la operación asíncrona: cb(err, data)
.
Se pide que:
- La función
execute
emita eventosbegin
yend
señalando el comienzo y final de la ejecución deasyncfun
- Deberá así mismo emitir un evento
result
con el resultado de la operación asíncrona. - Deberá emitir un evento
time
indicando el tiempo que ha tomado la ejecución en nanosegundos (useprocess.hrtime.bigint
(opens new window) para ello)
Por ejemplo, un código como:
const inspect = require("util").inspect;
const ins = (x) => inspect(x, {depth: Infinity, colors: true});
const fetch = require("node-fetch");
const WithTime = require("./with-time.js");
const withTime = new WithTime();
withTime.on('begin', (label) => console.log('About to execute '+label));
withTime.on('end', (label) => console.log('Done with execution of '+label));
withTime.on('result', (label, data) => console.log('Function '+label+' produced:\n'+ins(data)));
withTime.on('time', (label, t) => console.log('Function '+label+' took '+t+' nanoseconds'));
const readFile = (url, cb) => {
fetch(url)
.then((resp) => resp.json())
.then(function(data) {
cb(null, data);
})
.catch(e => console.log(`Buf!\n${e}`));
}
withTime.execute(readFile, 'https://jsonplaceholder.typicode.com/posts/3');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Debería producir una salida como está:
About to execute readFile
Function readFile produced:
{
userId: 1,
id: 3,
title: 'ea molestias quasi exercitationem repellat qui ipsa sit aut',
body: 'et iusto sed quo iure\n' +
'voluptatem occaecati omnis eligendi aut ad\n' +
'voluptatem doloribus vel accusantium quis pariatur\n' +
'molestiae porro eius odio et labore et velit aut'
}
Function readFile took 331675217 nanoseconds
Done with execution of readFile
2
3
4
5
6
7
8
9
10
11
12
13
Esta es una Solución
[~/.../networking-with-sockets-chapter-3-crguezl/event-emitter-tutorial(master)]$ cat with-time.js
La clase WithTime
extiende a EventEmitter
y define un método execute
que llama a la función asyncFunc
que es su primer parámetro pasándole como argumentos ...args
. Aprovechamos la callback para comprobar si hubieron errores en cuyo caso emitimos un evento error
con el error como argumento. Si no hubo errores emitimos los eventos result
, time
y end
const { EventEmitter } = require("events");
class WithTime extends EventEmitter {
// This function executes asyncFunc(...args)
execute(asyncFunc, ...args) {
let label = asyncFunc.name;
this.emit('begin', label);
let old = process.hrtime.bigint();
asyncFunc(...args, (err, data) => {
if (err) {
this.emit('error', err);
} else {
this.emit('result', label, data);
this.emit('time', label, process.hrtime.bigint() - old);
this.emit('end', label);
}
});
}
}
module.exports = WithTime;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24