这一小节是第四章的开篇,也是Node异步编程最基础、最常用的模式——事件发布/订阅(Event Publish/Subscribe),核心类就是EventEmitter。几乎所有Node核心模块(如fs、net、http、stream)都继承自它,它是Node“事件驱动”哲学的直接体现。
朴灵作者用很大篇幅讲解EventEmitter,因为在2013年,它是解决回调地狱的最主流方式之一(解耦、灵活、可组合)。
为什么需要事件发布/订阅?
- 回调函数是一对一的(一个操作一个回调),耦合度高。
- 实际场景往往“一对多”或“多对多”:
- 一个文件读取完成,可能需要触发多个处理(日志、缓存、响应客户端)。
- 一个服务器错误,需要通知多个监听者(监控、日志、重启)。
- 发布/订阅模式完美解耦:发布者只管emit事件,订阅者只管on监听,互不干扰。
EventEmitter 的核心 API
Node的events模块提供了EventEmitter类:
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {} // 自定义事件发射器
// 或直接用
const myEmitter = new EventEmitter();
主要方法:
-
on(eventName, listener) / addListener:订阅事件
myEmitter.on('event', () => console.log('事件发生!')); -
once(eventName, listener):只监听一次
myEmitter.once('once', () => console.log('只触发一次')); -
emit(eventName[, ...args]):发布(触发)事件
myEmitter.emit('event'); // 触发 -
off(eventName, listener) / removeListener:移除监听
const cb = () => console.log('要移除'); myEmitter.on('event', cb); myEmitter.off('event', cb); -
removeAllListeners([eventName]):移除所有或指定事件监听
-
listeners(eventName):获取某个事件的监听器数组
-
eventNames():获取所有事件名
经典示例:用EventEmitter组织异步操作
假设我们要读取文件 → 解析 → 保存 → 通知:
const EventEmitter = require('events');
const fs = require('fs');
class FileProcessor extends EventEmitter {}
const processor = new FileProcessor();
// 订阅多个处理
processor.on('read', (data) => console.log('读取完成:', data.length));
processor.on('parse', () => console.log('解析开始'));
processor.on('save', () => console.log('保存成功'));
// 异步流程
fs.readFile('data.txt', (err, data) => {
if (err) return console.error(err);
processor.emit('read', data); // 触发读取完成
// 模拟解析
setTimeout(() => {
processor.emit('parse');
// 模拟保存
setTimeout(() => {
processor.emit('save');
}, 100);
}, 200);
});
输出:
读取完成: xxx
解析开始
保存成功
优势:每个阶段独立,容易添加新监听者(比如加个日志监听),不改原有代码。
错误处理(重要!)
EventEmitter有一个约定:如果emit('error')且没有监听者,进程会崩溃!
正确写法:
myEmitter.on('error', (err) => console.error('出错了:', err));
myEmitter.emit('error', new Error('boom!'));
注意事项(书里强调的坑)
- 监听器数量默认最多10个,超了会警告(setMaxListeners调整)。
- emit是同步执行所有监听器(顺序执行)。
- 事件名是字符串(或Symbol,避免冲突)。