The events Module: EventEmitter
Objectives
By the end of this lesson, you should be able to:
- Create a class that extends
EventEmitterand emits custom events - Register multiple listeners for the same event
- Use
.once()and.removeListener()to control how a listener fires
💡 Why this matters: Module 2 (Events) of the frontend track covered DOM events, a click, a keypress. Node has its own, unrelated event system,
EventEmitter, and it’s the foundation a huge amount of Node itself is built on, streams (Lesson 8), HTTP servers (Lesson 6), and much of Express, all emit and listen for events using this exact pattern.
⚠️ A note on verification: every snippet and output in this lesson was actually run with Node.js.
Extending EventEmitter
const EventEmitter = require('events');
class OrderProcessor extends EventEmitter {
process(orderId) {
console.log(`processing order ${orderId}`);
this.emit('completed', orderId);
}
}
const processor = new OrderProcessor();
processor.on('completed', (orderId) => {
console.log(`listener 1: order ${orderId} is done`);
});
processor.on('completed', (orderId) => {
console.log(`listener 2: sending confirmation email for order ${orderId}`);
});
processor.process(101);
processing order 101
listener 1: order 101 is done
listener 2: sending confirmation email for order 101
class OrderProcessor extends EventEmitter (Module 2’s extends/super) gives every instance .on() and .emit(). .on('completed', callback) registers a listener, .emit('completed', orderId) fires it, passing orderId through as an argument. Multiple listeners can be registered for the same event, .emit() calls every one of them, in the exact order they were registered.
once() and removeListener()
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.once('greet', (name) => {
console.log(`(once) hello, ${name}`);
});
emitter.emit('greet', 'Sam');
emitter.emit('greet', 'Jordan');
(once) hello, Sam
.once() registers a listener that fires exactly one time, then automatically removes itself, the second .emit('greet', 'Jordan') finds no listener left, so nothing logs for it.
function onPing() {
console.log('ping received');
}
emitter.on('ping', onPing);
emitter.emit('ping');
emitter.removeListener('ping', onPing);
emitter.emit('ping');
console.log('done, no second ping should have logged');
ping received
done, no second ping should have logged
.removeListener(event, handler) unregisters a specific listener, this requires the handler to be a named function (not an inline arrow function), since removal works by matching the function reference itself. After removal, emitting the same event again finds no listener there.
Why This Pattern Matters for Express
Node’s built-in http module (next lesson) emits a 'request' event every time a request arrives, and Express is built directly on top of that. Streams (Lesson 8) emit 'data' and 'end' events as data arrives. EventEmitter isn’t a niche feature, it’s the plumbing connecting most of Node’s asynchronous, I/O-driven core.
Try It
- Create a class
TimerextendingEventEmitter, with astart()method that emits a'tick'event three times (using a loop), and register a listener that logs each tick. - Register two separate listeners for the same custom event, and confirm both fire, in registration order, from a single
.emit(). - Use
.once()to register a listener that should only respond to the very first event of its kind, and confirm a second.emit()doesn’t trigger it again. - Register a named listener function, emit the event once, remove the listener with
.removeListener(), then emit again, confirming the second emit produces no output.
Recap
- A class can
extends EventEmitterto gain.on()(register a listener) and.emit()(fire an event, calling every registered listener in order). .once()registers a listener that fires only the first time, then removes itself automatically..removeListener(event, namedHandler)unregisters a specific listener, requiring a named function reference to match against.
Next lesson: http, building a raw server with no framework.