An example for the event pattern in NodeJS

Events are natural to NodeJS, and the event emitter pattern is an important one in the ecosystem. In this post, I'll provide a simple example for emitting and listening to NodeJS events.

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. required 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 required 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.