Node.js: 事件系统 EventEmitter
最后更新:2026-08-26
Alice 是一名后端工程师,正在开发一个电商订单系统。最初她让订单创建函数直接调用发邮件、更新库存、记日志三个模块,结果每次新增功能都要修改订单核心代码,系统越来越脆弱。后来她用 EventEmitter 重构:订单模块只负责触发 order:created 事件,各服务独立监听,互不干扰。新增"短信通知"只需添加一个监听器,核心代码零改动。这种发布-订阅架构让系统维护成本降低了 60%。
1. 你将学到
- 使用
EventEmitter类创建事件发射器与继承扩展 - 使用
on()/emit()/off()/once()/removeAllListeners()管理事件 - 使用
listenerCount()/setMaxListeners()管理监听器数量与内存泄漏预警 - 创建自定义事件类并封装业务逻辑
- 正确处理
error事件与未捕获异常 - 理解 EventEmitter 在 Node.js 内置模块中的应用
- 使用发布-订阅模式构建松耦合的事件驱动架构
2. EventEmitter 核心概念
EventEmitter 是 Node.js 事件驱动的基石类,位于 events 模块。它维护一个事件名到监听器函数数组的映射,当 emit() 触发事件时,所有已注册的监听器按注册顺序同步执行。
flowchart LR
subgraph Emitter["EventEmitter(发布者)"]
E1[emit - order:created]
end
subgraph Listeners["监听器(订阅者)"]
L1[监听器1:发送邮件]
L2[监听器2:更新库存]
L3[监听器3:记录日志]
end
E1 --> L1
E1 --> L2
E1 --> L3
style Emitter fill:#e1f5fe
style Listeners fill:#f3e5f5
JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('greet', (name) => {
console.log(`Hello, ${name}!`);
});
emitter.emit('greet', 'Alice');
TEXT
📖 仅展示
Hello, Alice!
3. EventEmitter 常用方法速查
| 方法 | 说明 | 返回值 |
|---|---|---|
on(event, listener) |
注册监听器,可重复注册 | EventEmitter 实例 |
once(event, listener) |
注册一次性监听器,触发后自动移除 | EventEmitter 实例 |
emit(event, ...args) |
触发事件,传递参数 | true 有监听器 / false 无监听器 |
off(event, listener) |
移除指定监听器 | EventEmitter 实例 |
removeListener(event, listener) |
同 off(),旧 API |
EventEmitter 实例 |
removeAllListeners([event]) |
移除所有或指定事件的监听器 | EventEmitter 实例 |
prependListener(event, listener) |
将监听器添加到队列头部 | EventEmitter 实例 |
prependOnceListener(event, listener) |
头部添加一次性监听器 | EventEmitter 实例 |
listeners(event) |
返回事件监听器数组 | Function[] |
listenerCount(event) |
返回监听器数量 | number |
setMaxListeners(n) |
设置单个事件最大监听器数 | EventEmitter 实例 |
getMaxListeners() |
获取最大监听器数 | number |
eventNames() |
返回所有已注册事件名 | `(string |
rawListeners(event) |
返回包含 once 标记的原始监听器 |
Function[] |
▶ 示例:基本 on/emit/off 使用
JAVASCRIPT
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);
TEXT
📖 仅展示
[Listener] Order created: #1001
Has listeners: false
4. on vs once vs prependListener 对比
| 特性 | on() |
once() |
prependListener() |
|---|---|---|---|
| 触发次数 | 每次都触发 | 仅触发一次后自动移除 | 每次都触发 |
| 注册位置 | 队列尾部 | 队列尾部 | 队列头部(优先执行) |
| 典型场景 | 持续监听消息 | 初始化只执行一次 | 高优先级处理(如日志) |
| 自动移除 | 否 | 是 | 否 |
| 对应一次性版本 | — | once() |
prependOnceListener() |
▶ 示例:once 只触发一次
JAVASCRIPT
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');
TEXT
📖 仅展示
Server initialized on port 3000
Second emit had no effect
▶ 示例:prependListener 优先执行
JAVASCRIPT
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');
TEXT
📖 仅展示
Priority listener
Normal listener
5. 事件监听器管理
Node.js 默认单个事件最多 10 个监听器,超过会打印内存泄漏警告。这并非硬限制,而是提醒开发者检查是否忘记移除监听器。
▶ 示例:listenerCount 与内存泄漏警告
JAVASCRIPT
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());
TEXT
📖 仅展示
(node:1234) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 12 log listeners added. Use emitter.setMaxListeners() to increase limit
Listener count: 12
Max listeners: 10
▶ 示例:setMaxListeners 调整上限
JAVASCRIPT
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'));
TEXT
📖 仅展示
No warning: max set to 20
Listener count: 15
▶ 示例:removeAllListeners 清理
JAVASCRIPT
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'));
TEXT
📖 仅展示
tick listeners: 0
tock listeners: 1
6. 自定义事件类
实际开发中,通常继承 EventEmitter 创建具有业务语义的事件类,而非直接使用 EventEmitter 实例。
▶ 示例:继承 EventEmitter 创建订单事件类
JAVASCRIPT
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);
TEXT
📖 仅展示
[Email] Confirmation for order #2001
[Inventory] Deduct 3x Headphones
[Refund] Processing refund for #2001
7. error 事件与错误处理
EventEmitter 有特殊的 error 事件:如果触发了 error 事件但没有任何监听器,Node.js 会抛出异常并终止进程。这是 EventEmitter 中最重要的安全规则。
▶ 示例:未监听 error 导致进程崩溃
JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.emit('error', new Error('Database connection failed'));
TEXT
📖 仅展示
events.js:291
throw er; // Unhandled 'error' event
^
Error: Database connection failed
at Object.<anonymous> (app.js:4:20)
▶ 示例:正确处理 error 事件
JAVASCRIPT
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');
TEXT
📖 仅展示
[Error Handler] Database connection failed
Process continues running
8. 事件驱动 vs 回调 vs Promise 对比
| 特性 | 事件驱动(EventEmitter) | 回调(Callback) | Promise / async-await |
|---|---|---|---|
| 通信模式 | 一对多(发布-订阅) | 一对一 | 一对一 |
| 触发次数 | 可多次触发 | 仅触发一次 | 仅解决一次(resolve 一次) |
| 典型场景 | 消息广播、状态变更 | I/O 操作完成 | 异步操作的最终结果 |
| 耦合度 | 低(发布者不认识订阅者) | 高(调用者必须知道回调) | 中(链式或 await) |
| 可取消 | 可 off() 移除 | 不可取消 | 不可取消(可忽略) |
| 内置支持 | events 模块 |
全局约定 | 语言内置 |
| 错误处理 | error 事件 |
错误优先回调 | .catch() / try-catch |
9. Node.js 内置使用 EventEmitter 的模块
Node.js 大量内置模块继承自 EventEmitter,几乎所有流、服务器、进程对象都是事件发射器。
| 模块/对象 | 继承 EventEmitter | 常用事件 |
|---|---|---|
net.Server |
是 | connection, close, error |
http.Server |
是 | request, connection, close |
stream.Readable |
是 | data, end, error, close |
stream.Writable |
是 | drain, finish, error, close |
net.Socket |
是 | data, connect, end, error |
process |
是 | exit, uncaughtException, SIGINT |
child_process.ChildProcess |
是 | exit, message, error, close |
fs.watch() |
返回 EventEmitter | change, error |
▶ 示例:http.Server 使用 EventEmitter
JAVASCRIPT
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');
});
TEXT
📖 仅展示
Server listening on port 3000
[Connection] New client from ::ffff:127.0.0.1
[Request] GET /
▶ 示例:Readable Stream 使用 EventEmitter
JAVASCRIPT
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');
});
TEXT
📖 仅展示
[Data] Received: "Hello"
[Data] Received: " "
[Data] Received: "World"
[End] No more data
10. 综合示例:事件驱动任务调度器
构建一个 TaskScheduler 类,支持注册任务、触发事件、多个监听器响应。模拟真实场景中任务创建后的通知链。
JAVASCRIPT
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);
TEXT
📖 仅展示
=== 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
❓ 常见问题
Q EventEmitter 和浏览器中的事件有什么区别?
A Node.js 的 EventEmitter 是同步执行的,监听器按注册顺序依次运行;浏览器 DOM 事件是异步的,经过捕获/冒泡阶段,且事件对象结构与 Node.js 不同。
Q 忘记监听 error 事件会怎样?
A 如果 emit('error') 时没有注册任何 error 监听器,Node.js 会将错误作为未捕获异常抛出,进程直接退出(除非设置了 process.on('uncaughtException'))。
Q 一个事件最多能有多少个监听器?
A 默认上限为 10 个,超过会打印内存泄漏警告(不阻止执行)。可通过
emitter.setMaxListeners(n) 调整,设为 0 则无限制。Q emit() 的返回值是什么?
A 如果该事件至少有一个监听器,返回
true;如果没有任何监听器,返回 false。可用于判断事件是否被处理。Q 如何实现只监听一次就自动移除?
A 使用
once() 方法注册监听器,触发一次后自动移除,适用于初始化、一次性信号等场景。Q off() 和 removeListener() 有什么区别?
A 功能完全相同,
off() 是 Node.js v10 新增的 removeListener() 别名,语义更简洁,新代码推荐使用 off()。Q 监听器中的 this 指向什么?
A 使用 ES6 箭头函数时 this 继承外层作用域;使用普通函数时 this 指向 EventEmitter 实例,除非通过
bind() 绑定了其他对象。📖 小节
- 你将学到的核心概念与使用方法
- EventEmitter 核心概念的核心概念与使用方法
- EventEmitter 常用方法速查的核心概念与使用方法
- on vs once vs prependListener 对比的核心概念与使用方法
- 事件监听器管理的核心概念与使用方法
- 自定义事件类的核心概念与使用方法
- error 事件与错误处理的核心概念与使用方法
- 事件驱动 vs 回调 vs Promise 对比的核心概念与使用方法
📝 作业
- 完成本课所有代码示例,确保每个示例都能正确运行
- 修改综合示例,添加自己的扩展功能
- 查阅官方文档,找出本课未涉及的1-2个API并编写测试代码
- 思考:在实际项目中,你会如何应用本课学到的知识?
- 尝试将本课知识与前面课程的内容结合,构建一个小项目