JavaScript · Course navigation

JavaScript: the observer pattern with EventEmitter

Let several listeners react to the same event without the emitter knowing about them.

On this page
Before you start

JavaScript callbacks.

  • Let several listeners react to the same event without the emitter knowing about them.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

In the observer pattern a subject notifies any number of observers when something happens. The subject only knows that observers are callables, so new behaviour is added by subscribing, with no change to the subject. Node.js implements the pattern in the events module.

on registers a listener that runs for every event, once registers one that runs only for the next event, and emit calls the listeners in the order they were registered. The same idea appears in the browser as addEventListener, and as publish/subscribe in message queues.

Code example

JavaScript
const { EventEmitter } = require("node:events");

const bus = new EventEmitter();
const log = [];

bus.on("order", (id) => log.push("logged " + id));
bus.on("order", (id) => console.log("notify customer for order " + id));
bus.once("order", (id) => console.log("first order bonus for " + id));

bus.emit("order", 1);
bus.emit("order", 2);
console.log(log.join(", "));
Output
notify customer for order 1
first order bonus for 1
notify customer for order 2
logged 1, logged 2

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1const { EventEmitter } = require("node:events");
2
3const bus = new EventEmitter();
4const log = [];
5
6bus.on("order", (id) => log.push("logged " + id));
7bus.on("order", (id) => console.log("notify customer for order " + id));
8bus.once("order", (id) => console.log("first order bonus for " + id));
9
10bus.emit("order", 1);
11bus.emit("order", 2);
12console.log(log.join(", "));

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
A listener that records every event.—
—
A listener that is removed after its first call.—
—
All three listeners run in the order they were added; only two print.—
notify customer for order 1
first order bonus for 1
The once-listener is gone.—
notify customer for order 1
first order bonus for 1
notify customer for order 2
The recording listener saw both events.—
notify customer for order 1
first order bonus for 1
notify customer for order 2
logged 1, logged 2

Behind the scenes

How JavaScript processes this code

A JavaScript engine parses and runs the source. A function body runs when the function is called. console.log writes to the console.

Cost and common mistakes

emit is O(L) for L listeners.

  • emit('error') with no error listener throws and can crash the process. Always attach an error handler.
  • Listeners run synchronously inside emit. A slow listener delays the emitter and the other listeners.
  • Forgetting to remove listeners from long-lived emitters leaks memory; Node warns after 10 listeners for one event.

Try it yourself

How do you remove a listener?

Reveal answer and reasoning

Keep a reference to the function and call bus.off('order', fn), or bus.removeListener('order', fn).

Further reading