#Why event emitters exist
An event emitter is an object that keeps a list of functions per event name and calls them when that event is announced. One side emits ("an order was created, here it is"); any number of other pieces of code listen ("when an order is created, do this"). Neither side calls the other directly.
The problem it solves is coupling. Picture createOrder() in a growing app. First it saves the order. Then someone needs a metric, so it calls recordMetric(). Then the cache needs invalidating, then an audit log, then a webhook. Each new requirement edits createOrder, and the function ends up importing half the codebase. With an emitter, createOrder announces one fact — orderCreated — and every interested module subscribes on its own. Adding a new reaction no longer touches the producer.
The mental model is a radio station: it broadcasts on a named frequency and doesn't know or care who's tuned in — zero listeners, one, or twenty. Listeners tune in and out whenever they like. Node uses this design everywhere: an HTTP server emits request, a stream emits data and end, a child process emits exit. They all inherit from EventEmitter in node:events.
const { EventEmitter } = require('node:events');
const orderEvents = new EventEmitter();
async function saveOrder(order) {
return { ...order, id: 101 };
}
async function createOrder(order) {
const saved = await saveOrder(order);
orderEvents.emit('orderCreated', saved); // announce it; don't care who listens
return saved;
}
// Elsewhere — each module subscribes on its own:
orderEvents.on('orderCreated', (o) => console.log('metrics: order', o.id));
orderEvents.on('orderCreated', (o) => console.log('cache: invalidate orders for', o.user));
createOrder({ user: 'rohit', total: 40 }).then((o) => console.log('returned', o));orderCreatedmetrics: order 101
cache: invalidate orders for rohit
returned { user: 'rohit', total: 40, id: 101 }orderEventsis one shared emitter. In a real app it would live in its own module, so every file that requires it gets the same instance (see Shared state and the singleton pattern).- Two listeners are registered for
orderCreated.createOrderknows nothing about either; the metrics and cache modules registered themselves. createOrderawaitssaveOrder, which resolves with the order plusid: 101. Thenemit('orderCreated', saved)calls each listener in registration order, passingsavedas the argument: the metrics line, then the cache line.- Only after both listeners have returned does
createOrderreturn, soreturned …prints last. That ordering is the subject of emit runs listeners synchronously — the most important fact in this chapter. - The producer still has a contract to keep: the event name, when it fires (after the save, not before) and the payload shape are now a public API that listeners depend on.
Where emitters fit well: repeated notifications (a stream's chunks, a socket's messages), lifecycle signals (ready, close, error), and in-process decoupling of side effects that aren't part of the caller's result. Where they fit badly: anything the caller needs an answer from (use a function call or a promise), and anything that must survive a crash or reach another process (see Local events vs distributed messaging).