Card 05/ 09

ExampleDifficulty: Intermediate1 min

Observer in the Wild: Node's EventEmitter

on() subscribes, emit() notifies, and the browser's addEventListener is the same shape.

Node's EventEmitter, from the built-in events module, is a subject that any object can be or extend. Its on method attaches a listener for a named event, off detaches it, and emit calls every listener for that event with the arguments given. This card subscribes two listeners to an order event, detaches one, and emits again.

ts
import { EventEmitter } from "events";

const orders = new EventEmitter();

const sendEmail = (id: number) => console.log(`email for order ${id}`);
const updateStock = (id: number) => console.log(`stock updated for order ${id}`);

orders.on("placed", sendEmail);
orders.on("placed", updateStock);
console.log("listeners:", orders.listenerCount("placed"));
orders.emit("placed", 1001);

orders.off("placed", sendEmail);
console.log("listeners:", orders.listenerCount("placed"));
orders.emit("placed", 1002);
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
listeners: 2
email for order 1001
stock updated for order 1001
listeners: 1
stock updated for order 1002
Two listeners, then one, for the same event name.

The first emit reaches both listeners in the order they subscribed, and after off the second reaches only the stock update. The listing was compiled with TypeScript and run on Node 22, with a short type declaration for the events module standing in for Node's published types.

The participants map directly. The emitter is the subject, on and off are attach and detach, emit is notify, and each listener function is an observer. One difference from the template is the event name: one emitter can be a subject for many events, keeping a separate list for each.

A browser has the same shape on every element. button.addEventListener("click", handler) attaches an observer, removeEventListener detaches it, and a click notifies every handler: the same pattern under different names.