# 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 }
Copied!
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
Copied!
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 }
Copied!
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
Copied!
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 }
Copied!
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
Copied!
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 }
Copied!
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
Copied!
2
3
# listenerCount and rawListeners
The method listenerCount
returns the number of listeners for a given event:
> myEmitter.listenerCount('eventOne') 1
Copied!
2
The method rawListeners
returns an array of listeners for a given event:
> myEmitter.rawListeners('eventOne') [ [Function: c2] ]
Copied!
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');
Copied!
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
Copied!
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
Copied!
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;
Copied!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24