Node系列 · Node基础:EventEmitter
Node 的事件驱动模型几乎全部基于 EventEmitter:
fs.ReadStream的data事件、net.Socket的connect事件、process的exit事件、http.Server的request事件——背后都是它。理解 EventEmitter 的发布订阅机制、最大监听数陷阱和错误事件特殊处理,就理解了 Node 一半的 API 设计模式。
一、EventEmitter 是什么
EventEmitter 是 Node 内置的发布订阅(pub/sub)实现:
- 发布者调用
emit(eventName, ...args)触发事件 - 订阅者调用
on(eventName, handler)注册回调 - 事件名是任意字符串,回调按注册顺序同步触发
二、基本 API
2.1 创建与订阅
event-demo.jsconst { EventEmitter } = require('node:events');
class MyEmitter extends EventEmitter {}
const bus = new MyEmitter();
// on = addListener 的别名
bus.on('greet', (name) => {
console.log(`hello, ${name}`);
});
// once:只触发一次后自动移除
bus.once('firstTime', () => {
console.log('this runs only once');
});
// emit 触发事件
bus.emit('greet', 'Alice'); // 'hello, Alice'
bus.emit('greet', 'Bob'); // 'hello, Bob'
bus.emit('firstTime'); // 'this runs only once'
bus.emit('firstTime'); // (无输出,监听器已自动移除)2.2 移除监听器
const handler = (data) => console.log('received:', data);
bus.on('data', handler);
// 移除指定监听器
bus.off('data', handler); // Node 10+
// 等价老写法:bus.removeListener('data', handler)
// 移除某事件的所有监听器
bus.removeAllListeners('data');
// 不传参:移除所有事件的所有监听器
bus.removeAllListeners();TIP
off 是 removeListener 的别名(Node 10+),与浏览器 removeEventListener 命名风格对齐。
2.3 监听器数量
bus.on('data', () => {});
bus.on('data', () => {});
bus.on('data', () => {});
console.log(bus.listenerCount('data')); // 3
console.log(bus.eventNames()); // ['data']三、最大监听数限制
EventEmitter 默认限制单个事件最多 10 个监听器。超过会触发警告,但不会崩溃:
const bus = new EventEmitter();
for (let i = 0; i < 12; i++) {
bus.on('data', () => {});
}
// 输出(stderr):
// (node:1234) MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
// 12 data listeners added to [EventEmitter]. Use emitter.setMaxListeners() to increase limit3.1 为什么有这限制?
防止"监听器泄漏"——开发时忘记移除监听器,导致内存里堆积越来越多的回调。10 是一个合理上限,超过就该审视设计。
3.2 调整上限
// 单个实例调整
bus.setMaxListeners(20);
// 全局调整(不推荐,影响所有 EventEmitter)
// EventEmitter.defaultMaxListeners = 20;
// 用 INFINITE 符号表示不限制
const { INFINITE } = require('node:events');
bus.setMaxListeners(INFINITE);3.3 识别泄漏
function checkListeners(emitter, eventName, expected) {
const count = emitter.listenerCount(eventName);
if (count > expected) {
console.warn(`warning: ${eventName} has ${count} listeners (expected ${expected})`);
}
}四、错误事件(error)
error 事件有特殊处理:如果发布 error 但没有监听器,Node 会抛出未捕获异常并导致进程崩溃。
const bus = new EventEmitter();
bus.emit('error', new Error('boom'));
// 输出:
// Error: boom
// at ...
// [进程崩溃,exit code 非 0]DANGER
所有会发出 error 事件的 EventEmitter,必须监听 error。fs 流、http 请求、process 等都是。
bus.on('error', (err) => {
console.error('捕获到错误:', err.message);
});
bus.emit('error', new Error('boom')); // 被捕获,进程不退出如果业务不关心错误细节,可以注册一个空 handler 避免崩溃:
bus.on('error', () => {}); // 空 handler,仅为防止崩溃WARNING
空 handler 会静默吞掉所有错误。生产环境至少要 console.error 记录日志。
五、同步还是异步触发?
emit 调用是同步的——所有监听器在 emit 调用栈内执行:
const bus = new EventEmitter();
bus.on('data', () => console.log('A'));
bus.on('data', () => console.log('B'));
bus.on('data', () => console.log('C'));
console.log('before emit');
bus.emit('data');
console.log('after emit');
// 输出:
// before emit
// A
// B
// C
// after emit监听器执行顺序:
- 按注册顺序同步触发
- 在
emit调用栈内全部跑完才返回 - 监听器抛异常会中断后续监听器
bus.on('data', () => { throw new Error('first'); });
bus.on('data', () => console.log('B'); // 不会被调用
bus.emit('data'); // 异常往上抛六、典型应用:Node 内置类的继承链
EventEmitter 是 Node 事件模型的根基,很多内置类都继承自它:
这就解释了为什么 process.on('exit', ...)、http.Server.on('request', ...)、fs.createReadStream().on('data', ...) 都长得一样——本质都是 EventEmitter 的 on / emit。
七、自定义类继承 EventEmitter
业务类继承 EventEmitter 就能发布自定义事件。下面的 TaskQueue 在关键节点 emit,订阅方拿异步通知:
const { EventEmitter } = require('node:events');
class TaskQueue extends EventEmitter {
constructor() {
super();
this.tasks = [];
}
add(task) {
this.tasks.push(task);
this.emit('added', task); // 通知订阅者
}
process() {
if (this.tasks.length === 0) {
this.emit('empty');
return;
}
const task = this.tasks.shift();
this.emit('processing', task);
// ... 实际处理逻辑
this.emit('processed', task);
}
}
const queue = new TaskQueue();
queue.on('added', (task) => console.log('+ added:', task));
queue.on('processed', (task) => console.log('✓ done:', task));
queue.on('empty', () => console.log('queue empty'));
queue.add({ id: 1, name: 'send email' });
queue.add({ id: 2, name: 'resize image' });
queue.process();
queue.process();
queue.process(); // empty7.1 Promise 化 EventEmitter
EventEmitter 不返回 Promise,但常被 Promise 化(一次性的事件变 await):
function once(emitter, eventName) {
return new Promise((resolve, reject) => {
emitter.once(eventName, resolve);
emitter.once('error', reject);
});
}
// 用法:等待 stream 的 'open' 事件
const { once } = require('node:events');
const stream = fs.createReadStream('./big.txt');
await once(stream, 'open');
console.log('文件已打开');
await once(stream, 'close');
console.log('文件已关闭');Node 16+ 内置 events.once(),不需要自己包装。
八、EventEmitter vs EventTarget vs Promise
| API | 一对多 | 一次性 | 返回值 | 标准 |
|---|---|---|---|---|
| EventEmitter | ✅ 多监听器 | 用 once | 无 | Node 私货 |
| EventTarget(浏览器) | ✅ 多监听器 | 用 { once: true } | 无 | DOM 标准 |
| Promise | ❌ 单回调 | ✅ 天然一次性 | thenable | ECMAScript |
何时选哪个:
| 场景 | 推荐 |
|---|---|
| Node 内置 API(stream、process、http) | EventEmitter(不得不) |
| 一次性结果(HTTP 请求、文件读取) | Promise(更现代) |
| 浏览器代码或跨平台 | EventTarget(DOM 标准) |
| 高频多订阅(发布订阅总线) | EventEmitter / EventTarget |
九、最佳实践
| 场景 | 推荐 |
|---|---|
监听 error | 必须监听,否则进程崩溃 |
| 监听器泄漏 | 用 setMaxListeners(20) 显式声明(说明确实需要) |
| 一次性事件 | once 或 events.once() |
| 清理监听器 | 在资源生命周期结束时 removeListener 或 removeAllListeners |
| 异步事件流 | 配合 stream(EventEmitter + 流控制) |
| 多实例隔离 | 每个实例独立订阅,不全局共享 bus |
| 调试监听器 | getEventListeners(emitter, eventName)(Node 15.2+) |
9.1 调试技巧
const { getEventListeners } = require('node:events');
// 查某事件的监听器
const handlers = getEventListeners(bus, 'data');
console.log('data 事件的监听器数量:', handlers.length);十、常见错误
| 错误 | 原因 | 解决 |
|---|---|---|
MaxListenersExceededWarning | 单事件监听器超过 10 | setMaxListeners 或检查重复注册 |
未捕获 error 事件崩溃 | 没监听 error | 加 bus.on('error', handler) |
| 监听器永远不触发 | 事件名拼写错 | 检查 emit 和 on 的事件名一致 |
| 内存泄漏 | 监听器没移除 | 在合适的生命周期调 removeListener |
emit 内异常中断后续监听器 | 监听器抛异常 | 监听器内部 try/catch |
十一、小结
- EventEmitter 是 Node 事件模型的根基;内置类(stream / http / net / process)几乎都继承它
- API:
on/once/emit/off/removeAllListeners/listenerCount emit同步触发所有监听器,按注册顺序- 默认最大监听数 10,超过警告但不崩溃;用
setMaxListeners调整 error事件无监听器时进程崩溃——必须监听- 一次性事件用
once或events.once();一次性结果优先用 Promise - 高频多订阅场景用 EventEmitter;一次性结果用 Promise;跨平台用 EventTarget
