An example for the event pattern in NodeJS
1. Events as core building blocks in Node
Events in NodeJS are governed by the EventEmitter
class of the Events
module, and as such, it needs to be instantiated if we want to work with events.
Although event listeners involve invocations of callbacks, events are synchronous.
1.1. Event
When something needs to be published, for example, a result that has been calculated or a piece of data has been fetched, an event can be emitted. Emitted events have names and arguments.
The name of the event is unique, and the argument (the result or the data) will be carried over to the listener. One way of emitting event is using the emit
method on the EventEmitter
instance.
1.2. Listener
A listener is a function that gets invoked when the event specified by the event name is emitted.
Listeners are attached to the EventEmitter
instance through the on
method. The first argument of on
is the name of the event (which is supposed to be unique), the second argument is a callback function.
The argument of the callback function is the result or data that have been emitted (see 1.1.).
1.3. One instance
It’s important to call the emit
and on
methods on the same EventEmitter
instance, otherwise the listener won’t catch the event object.
2. Example
Let’s take a simple example and see how the pattern works. The example will be around some super-hard calculations: the perimeter and area of a rectangle.
These values will be emitted under their unique event names, and the listener will be placed in another file. The EventEmitter
will be instantiated in a separate module.
2.1. The event emitter
The name of the module is event-emitter.js
, and it might be something like this:
const { EventEmitter } = require('events')
const emitter = new EventEmitter()
module.exports = emitter
Events will be emitted and listened on this instance.
2.2. Emitting the perimeter and area
Let’s say that all files are placed in one folder.
The module that calculates the perimeter and area is called calculate.js
, and it can look like this:
const emitter = require('./event-emitter')
function calculatePerimeter(length, width) {
const perimeter = 2 * (length + width)
emitter.emit('perimeter', perimeter)
}
function calculateArea(length, width) {
const area = length * width
emitter.emit('area', area)
}
module.exports = {
calculateArea,
calculatePerimeter,
}
It’s important that we don’t instantiate EventEmitter
again, but require
it from event-emitter.js
. require
d modules in NodeJS are singletons by default.
The calculatePerimeter
and calculateArea
functions emit the perimeter and area under the event names of perimeter
and area
, respectively.
2.2. Event handlers
It’s a good idea to collect logically related event handlers (listeners) in one module, and import that module where it needs to be used.
Let’s call the listener module event-handler.js
, and we can listen to the perimeter
and area
events like this:
const emitter = require('./event-emitter')
function handlers() {
emitter.on('perimeter', (perimeter) => {
console.log(`The perimeter of the rectangle is ${ perimeter } cm.`)
})
emitter.on('area', (area) => {
console.log(`The area of the rectangle is ${ area } cm2.`)
})
}
module.exports = {
handlers
}
Again, the same emitter
instance is used, and the perimeter and area are simply logged to the console when the perimeter
and area
events are emitted in another part of the code.
2.3. Putting it together
Let’s put the pieces of the puzzle together. In a file called index.js
, we can write something like this:
const { handlers } = require('./event-handler')
const { calculateArea, calculatePerimeter } = require('./calculate')
function main() {
handlers()
const LENGTH = 5
const WIDTH = 6
calculatePerimeter(LENGTH, WIDTH)
calculateArea(LENGTH, WIDTH)
}
main()
The event emitter functions and the handler module are require
d first. In a function called main
, handlers
is invoked first. It’s important to register the listeners first and emit the events second; otherwise the listeners won’t be able to catch the events.
No magic here, the calculatePerimeter
and calculateArea
functions are called, and the perimeter
and area
events are emitted with the relevant geometrical results.
If the node index.js
command is run from the terminal, the results should be seen in the console.
3. Summary
The event pattern is an important one in NodeJS as the library is built around events.
Emitted events are captured by the event listeners, and, although listeners have callbacks, this is synchronous process.
Thanks for reading, and see you next time.