Node.js: Events and EventEmitter
Last updated: 2026-08-26
Alice is a backend engineer developing an e-commerce order system. Initially, she had the order creation function directly call three modules—sending emails, updating inventory, and logging—but this meant that every time she added a new feature, she had to modify the core order code, making the system increasingly fragile. Later, she refactored the code using EventEmitter: the order module is now responsible only for triggering the order:created event, while each service listens independently without interfering with one another. Adding “SMS notifications” requires only adding a listener, with zero changes to the core code. This publish-subscribe architecture reduced the system’s maintenance costs by 60%.
1. What You'll Learn
- Creating event emitters and extending them using the
EventEmitterclass - Use
on()/emit()/off()/once()/removeAllListeners()to manage events - Use
listenerCount()/setMaxListeners()to manage the value of listeners and memory leak alerts - Create a custom event class and encapsulate the business logic
- Properly Handling
errorEvents and Uncaught Exceptions - Understanding the use of
EventEmitterin Node.js's built-in modules - Building a loosely coupled, event-driven architecture using the publish-subscribe pattern
2. Core Concepts of EventEmitter
EventEmitter is the foundational class for Node.js's event-driven architecture, located in the events module. It maintains a mapping from event names to arrays of listener functions. When emit() triggers an event, all registered listeners are executed synchronously in the order they were registered.
flowchart LR
subgraph Emitter["EventEmitter(Posted by)"]
E1[emit - order:created]
end
subgraph Listeners["Listener(Subscribers)"]
L1[Listener1:Send an Email]
L2[Listener2:Update Inventory]
L3[Listener3:Log Entry]
end
E1 --> L1
E1 --> L2
E1 --> L3
style Emitter fill:#e1f5fe
style Listeners fill:#f3e5f5
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('greet', (name) => {
console.log(`Hello, ${name}!`);
});
emitter.emit('greet', 'Alice');
Hello, Alice!
3. Quick Reference for Common EventEmitter Methods
| Method | Description | Return Value |
|---|---|---|
on(event, listener) |
Register a listener; can be registered multiple times | EventEmitter instance |
once(event, listener) |
Register a one-time listener that is automatically removed upon triggering | EventEmitter instance |
emit(event, ...args) |
Trigger an event, pass parameters | true Has a listener / false Has no listener |
off(event, listener) |
Remove a specified listener | EventEmitter instance |
removeListener(event, listener) |
Same as off(), old API |
EventEmitter instance |
removeAllListeners([event]) |
Remove listeners for all or specified events | EventEmitter instance |
prependListener(event, listener) |
Add a listener to the front of the queue | EventEmitter instance |
prependOnceListener(event, listener) |
Add a one-time listener at the top | EventEmitter instance |
listeners(event) |
Return the event listener array | Function[] |
listenerCount(event) |
Number of listeners | number |
setMaxListeners(n) |
Set the maximum number of listeners for a single event | EventEmitter instance |
getMaxListeners() |
Get the maximum number of listeners | number |
eventNames() |
Return all registered event names | `(string |
rawListeners(event) |
Return the original listener tagged with once |
Function[] |
▶ Example: Basic use of on/emit/off
const EventEmitter = require('events');
const emitter = new EventEmitter();
function onOrderCreated(order) {
console.log(`[Listener] Order created: #${order.id}`);
}
emitter.on('order:created', onOrderCreated);
emitter.emit('order:created', { id: 1001, product: 'Laptop', qty: 2 });
emitter.off('order:created', onOrderCreated);
const hasListeners = emitter.emit('order:created', { id: 1002, product: 'Mouse', qty: 5 });
console.log('Has listeners:', hasListeners);
[Listener] Order created: #1001
Has listeners: false
4. on vs once vs prependListener Comparison
| Characteristics | on() |
once() |
prependListener() |
|---|---|---|---|
| Number of Triggers | Triggers Every Time | Triggers Only Once, Then Removes It Automatically | Triggers Every Time |
| Registration Location | End of Queue | End of Queue | Beginning of Queue (Executed First) |
| Typical Scenarios | Continuous Message Monitoring | Initialization Performed Only Once | High-Priority Processing (e.g., Logging) |
| Auto-remove | No | Yes | No |
| Corresponding to the single-use version | — | once() |
prependOnceListener() |
▶ Example: "once" triggers only once
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.once('server:ready', (port) => {
console.log(`Server initialized on port ${port}`);
});
emitter.emit('server:ready', 3000);
emitter.emit('server:ready', 3001);
console.log('Second emit had no effect');
Server initialized on port 3000
Second emit had no effect
▶ Example: prependListener is executed first
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('data', () => console.log('Normal listener'));
emitter.prependListener('data', () => console.log('Priority listener'));
emitter.emit('data');
Priority listener
Normal listener
5. Event Listener Management
By default, Node.js allows a maximum of 10 listeners per event; exceeding this limit will trigger a memory leak warning. This is not a hard limit, but rather a reminder for developers to check whether they have forgotten to remove any listeners.
▶ Example: listenerCount and Memory Leak Warnings
const EventEmitter = require('events');
const emitter = new EventEmitter();
for (let i = 0; i < 12; i++) {
emitter.on('log', () => console.log(`Listener ${i}`));
}
console.log('Listener count:', emitter.listenerCount('log'));
console.log('Max listeners:', emitter.getMaxListeners());
(node:1234) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 12 log listeners added. Use emitter.setMaxListeners() to increase limit
Listener count: 12
Max listeners: 10
▶ Example: Adjusting the upper limit with setMaxListeners
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.setMaxListeners(20);
for (let i = 0; i < 15; i++) {
emitter.on('task', () => {});
}
console.log('No warning: max set to', emitter.getMaxListeners());
console.log('Listener count:', emitter.listenerCount('task'));
No warning: max set to 20
Listener count: 15
▶ Example: removeAllListeners cleanup
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('tick', () => console.log('tick A'));
emitter.on('tick', () => console.log('tick B'));
emitter.on('tock', () => console.log('tock A'));
emitter.removeAllListeners('tick');
console.log('tick listeners:', emitter.listenerCount('tick'));
console.log('tock listeners:', emitter.listenerCount('tock'));
tick listeners: 0
tock listeners: 1
6. Custom Event Classes
In actual development, it is common to inherit from EventEmitter to create event classes with business semantics, rather than using EventEmitter instances directly.
▶ Example: Creating an order event class by extending EventEmitter
const EventEmitter = require('events');
class OrderEmitter extends EventEmitter {
create(order) {
this.emit('order:created', order);
}
cancel(orderId) {
this.emit('order:cancelled', { orderId, cancelledAt: new Date().toISOString() });
}
ship(orderId, trackingNo) {
this.emit('order:shipped', { orderId, trackingNo });
}
}
const orderEvents = new OrderEmitter();
orderEvents.on('order:created', (order) => {
console.log(`[Email] Confirmation for order #${order.id}`);
});
orderEvents.on('order:created', (order) => {
console.log(`[Inventory] Deduct ${order.qty}x ${order.product}`);
});
orderEvents.on('order:cancelled', ({ orderId }) => {
console.log(`[Refund] Processing refund for #${orderId}`);
});
orderEvents.create({ id: 2001, product: 'Headphones', qty: 3 });
orderEvents.cancel(2001);
[Email] Confirmation for order #2001
[Inventory] Deduct 3x Headphones
[Refund] Processing refund for #2001
7. The error Event and Error Handling
EventEmitter has a special error event: if the error event is triggered but there are no listeners, Node.js will throw an exception and terminate the process. This is the most important safety rule in EventEmitter.
▶ Example: Process crash due to failure to handle an error
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.emit('error', new Error('Database connection failed'));
events.js:291
throw er; // Unhandled 'error' event
^
Error: Database connection failed
at Object.<anonymous> (app.js:4:20)
▶ Example: Handling the error event correctly
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('error', (err) => {
console.error(`[Error Handler] ${err.message}`);
});
emitter.emit('error', new Error('Database connection failed'));
console.log('Process continues running');
[Error Handler] Database connection failed
Process continues running
8. Comparison of Event-Driven, Callbacks, and Promises
| Feature | Event-driven (EventEmitter) | Callback | Promise / async-await |
|---|---|---|---|
| Communication Mode | One-to-many (publish-subscribe) | One-to-one | One-to-one |
| Number of Triggers | Can be triggered multiple times | Triggers only once | Resolves only once |
| Typical Scenarios | Message Broadcasting, State Changes | Completion of I/O Operations | Final Results of Asynchronous Operations |
| Coupling | Low (the publisher does not know the subscriber) | High (the caller must know the callback) | Medium (chaining or await) |
| Cancelable | Can be removed with off() | Non-cancelable | Non-cancelable (ignorable) |
| Built-in Support | events Module |
Global Conventions | Built-in Language |
| Error Handling | error Event |
Error Priority Callback | .catch() / try-catch |
9. Built-in Node.js Modules That Use EventEmitter
Many of Node.js's built-in modules inherit from EventEmitter; almost all stream, server, and process objects are event emitters.
| Module/Object | Inherits from EventEmitter | Common Events |
|---|---|---|
net.Server |
Yes | connection, close, error |
http.Server |
Yes | request, connection, close |
stream.Readable |
Yes | data, end, error, close |
stream.Writable |
Yes | drain, finish, error, close |
net.Socket |
Yes | data, connect, end, error |
process |
Yes | exit, uncaughtException, SIGINT |
child_process.ChildProcess |
Yes | exit, message, error, close |
fs.watch() |
Back to EventEmitter | change, error |
▶ Example: http.Server using EventEmitter
const http = require('http');
const server = http.createServer();
server.on('request', (req, res) => {
console.log(`[Request] ${req.method} ${req.url}`);
res.end('OK');
});
server.on('connection', (socket) => {
console.log(`[Connection] New client from ${socket.remoteAddress}`);
});
server.listen(3000, () => {
console.log('Server listening on port 3000');
});
Server listening on port 3000
[Connection] New client from ::ffff:127.0.0.1
[Request] GET /
▶ Example: Readable Stream using EventEmitter
const { Readable } = require('stream');
const readable = Readable.from(['Hello', ' ', 'World']);
readable.on('data', (chunk) => {
console.log(`[Data] Received: "${chunk}"`);
});
readable.on('end', () => {
console.log('[End] No more data');
});
[Data] Received: "Hello"
[Data] Received: " "
[Data] Received: "World"
[End] No more data
10. Comprehensive Example: Event-Driven Task Scheduler
Create a TaskScheduler class that supports task registration, event triggering, and responses from multiple listeners. Simulate the notification chain that occurs after a task is created in a real-world scenario.
const EventEmitter = require('events');
class TaskScheduler extends EventEmitter {
constructor() {
super();
this.tasks = new Map();
this.nextId = 1;
}
addTask(name, payload) {
const id = this.nextId++;
const task = {
id,
name,
payload,
createdAt: new Date().toISOString(),
status: 'pending'
};
this.tasks.set(id, task);
this.emit('task:added', task);
return task;
}
startTask(id) {
const task = this.tasks.get(id);
if (!task) {
this.emit('error', new Error(`Task #${id} not found`));
return;
}
task.status = 'running';
task.startedAt = new Date().toISOString();
this.emit('task:started', task);
}
completeTask(id, result) {
const task = this.tasks.get(id);
if (!task) {
this.emit('error', new Error(`Task #${id} not found`));
return;
}
task.status = 'completed';
task.result = result;
task.completedAt = new Date().toISOString();
this.emit('task:completed', task);
}
failTask(id, reason) {
const task = this.tasks.get(id);
if (!task) {
this.emit('error', new Error(`Task #${id} not found`));
return;
}
task.status = 'failed';
task.reason = reason;
task.failedAt = new Date().toISOString();
this.emit('task:failed', task);
}
getStats() {
const stats = { total: this.tasks.size, pending: 0, running: 0, completed: 0, failed: 0 };
for (const task of this.tasks.values()) {
stats[task.status]++;
}
return stats;
}
}
const scheduler = new TaskScheduler();
scheduler.on('error', (err) => {
console.error(`[ERROR] ${err.message}`);
});
scheduler.on('task:added', (task) => {
console.log(`[Logger] Task #${task.id} "${task.name}" added at ${task.createdAt}`);
});
scheduler.on('task:added', (task) => {
console.log(`[Notifier] New task available: ${task.name}`);
});
scheduler.on('task:started', (task) => {
console.log(`[Executor] Task #${task.id} is now running...`);
});
scheduler.on('task:completed', (task) => {
console.log(`[Reporter] Task #${task.id} completed with result: ${task.result}`);
console.log(`[Cleaner] Releasing resources for task #${task.id}`);
});
scheduler.on('task:failed', (task) => {
console.log(`[Alerter] Task #${task.id} failed: ${task.reason}`);
});
console.log('=== Adding Tasks ===');
const t1 = scheduler.addTask('Data Import', { source: 'api.example.com', rows: 5000 });
const t2 = scheduler.addTask('Report Generation', { format: 'PDF', quarter: 'Q4' });
console.log('\n=== Starting Tasks ===');
scheduler.startTask(t1.id);
scheduler.startTask(t2.id);
console.log('\n=== Completing / Failing Tasks ===');
scheduler.completeTask(t1.id, '5000 rows imported successfully');
scheduler.failTask(t2.id, 'PDF renderer service unavailable');
console.log('\n=== Stats ===');
console.log(scheduler.getStats());
console.log('\n=== Invalid Operation ===');
scheduler.startTask(999);
=== Adding Tasks ===
[Logger] Task #1 "Data Import" added at 2025-07-03T10:00:00.000Z
[Notifier] New task available: Data Import
[Logger] Task #2 "Report Generation" added at 2025-07-03T10:00:01.000Z
[Notifier] New task available: Report Generation
=== Starting Tasks ===
[Executor] Task #1 is now running...
[Executor] Task #2 is now running...
=== Completing / Failing Tasks ===
[Reporter] Task #1 completed with result: 5000 rows imported successfully
[Cleaner] Releasing resources for task #1
[Alerter] Task #2 failed: PDF renderer service unavailable
=== Stats ===
{ total: 2, pending: 0, running: 0, completed: 1, failed: 1 }
=== Invalid Operation ===
[ERROR] Task #999 not found
❓ FAQ
error event?error listeners are registered when emit('error') is called, Node.js will throw the error as an uncaught exception, and the process will exit immediately (unless process.on('uncaughtException') is set).emitter.setMaxListeners(n); setting it to 0 removes the limit.emit()?true; if it has no listeners, it returns false. This can be used to determine whether the event has been handled.once() method to register the listener; it will be automatically removed after being triggered once. This is suitable for scenarios such as initialization and one-time signals.off() and removeListener()?off() is a new alias introduced in Node.js v10 removeListener() that offers more concise syntax; it is recommended to use off() in new code.this refer to in a listener?this inherits the outer scope; when using regular functions, this refers to the EventEmitter instance, unless it has been bound to another object via bind().📖 Summary
- Key Concepts and How to Apply Them
- Core Concepts and Usage of EventEmitter
- Core Concepts and Usage of the EventEmitter Quick Reference for Common Methods
- Key Concepts and Usage of "on," "once," and "prependListener": A Comparison
- Core Concepts and Usage of Event Listeners
- Core Concepts and Usage of Custom Event Classes
- Core Concepts and Usage of the
errorEvent and Error Handling - Key Concepts and Usage of Event-Driven, Callbacks, and Promises: A Comparison
📝 Exercises
- Complete all the code examples in this lesson and make sure each one runs correctly.
- Modify the comprehensive example and add your own extensions
- Review the official documentation, identify 1–2 APIs not covered in this lesson, and write test code for them.
- Reflection: How would you apply what you’ve learned in this lesson to a real-world project?
- Try to combine what you’ve learned in this lesson with material from previous lessons to build a small project.