EventEmitter 是 Node.js 事件驱动架构的核心模块,它实现了观察者模式,允许对象订阅和发布事件。这种模式在 Node.js 生态系统中无处不在,从 HTTP 服务器到文件流处理都依赖它。
事件驱动架构特别适合 I/O 密集型的应用场景,比如:
相比传统的同步或线程模型,事件驱动模型有以下优势:
EventEmitter 的实现基于以下几个关键概念:
重要提示:虽然 emit() 是同步的,但通常我们会用它来触发异步操作。理解这一点对避免竞态条件很重要。
在 Node.js 的不同版本中,继承 EventEmitter 的方式有所演变:
javascript复制const util = require('util');
const EventEmitter = require('events');
function MyEmitter() {
EventEmitter.call(this);
}
util.inherits(MyEmitter, EventEmitter);
javascript复制const EventEmitter = require('events');
class MyEmitter extends EventEmitter {
constructor() {
super();
// 初始化代码
}
}
javascript复制emitter.on('event', listener); // 持久监听
emitter.once('event', listener); // 一次性监听
emitter.prependListener('event', listener); // 添加到监听器数组开头
emitter.prependOnceListener('event', listener); // 一次性+前置
javascript复制// 不推荐
emitter.on('data', (chunk) => { /*...*/ });
// 推荐
function dataHandler(chunk) { /*...*/ }
emitter.on('data', dataHandler);
emitter.removeListener('data', dataHandler);
javascript复制emitter.setMaxListeners(20); // 超过会发出警告
javascript复制const EVENTS = {
START: 'start',
DATA: 'data',
END: 'end',
ERROR: 'error'
};
EventEmitter 提供了两个特殊事件用于反射:
实用案例:自动初始化事件源
javascript复制class Sensor extends EventEmitter {
constructor() {
super();
this._interval = null;
this.on('newListener', (event) => {
if (event === 'data' && !this._interval) {
this._startSampling();
}
});
this.on('removeListener', (event) => {
if (event === 'data' && this.listenerCount('data') === 0) {
this._stopSampling();
}
});
}
_startSampling() {
this._interval = setInterval(() => {
this.emit('data', Math.random());
}, 1000);
}
_stopSampling() {
clearInterval(this._interval);
this._interval = null;
}
}
javascript复制let batch = [];
let isProcessing = false;
emitter.on('data', (item) => {
batch.push(item);
if (!isProcessing && batch.length >= 100) {
processBatch();
}
});
function processBatch() {
isProcessing = true;
// 处理批量数据
console.log(`Processing ${batch.length} items`);
batch = [];
isProcessing = false;
}
javascript复制// 不推荐:每个客户端一个监听器
clients.forEach(client => {
client.on('data', handler);
});
// 推荐:单个代理监听器
const proxy = new EventEmitter();
clients.forEach(client => {
client.on('data', (data) => {
proxy.emit('data', { client, data });
});
});
proxy.on('data', ({ client, data }) => {
// 统一处理逻辑
});
当应用扩展到多进程或多机器时,需要分布式事件解决方案:
| 方案 | 适用场景 | 特点 |
|---|---|---|
| Redis Pub/Sub | 简单实时通知 | 轻量级,持久化可选 |
| RabbitMQ | 可靠消息队列 | 消息确认,路由灵活 |
| Kafka | 高吞吐量流处理 | 分区,持久化,重放 |
| ZeroMQ | 高性能IPC | 低延迟,多种模式 |
Redis 集成示例
javascript复制const redis = require('redis');
const subscriber = redis.createClient();
const publisher = redis.createClient();
// 本地EventEmitter桥接
class DistributedEmitter extends EventEmitter {
constructor(channel) {
super();
this.channel = channel;
subscriber.on('message', (chn, message) => {
if (chn === channel) {
const { event, data } = JSON.parse(message);
super.emit(event, data);
}
});
subscriber.subscribe(channel);
}
emit(event, data) {
publisher.publish(this.channel, JSON.stringify({ event, data }));
return super.emit(event, data); // 本地也触发
}
}
EventEmitter 可以用于实现事件溯源(Event Sourcing):
javascript复制class EventStore extends EventEmitter {
constructor() {
super();
this._events = [];
}
emit(event, ...args) {
const timestamp = Date.now();
const eventRecord = { event, args, timestamp };
// 持久化事件
this._events.push(eventRecord);
// 触发事件
super.emit(event, ...args);
// 触发通用事件记录
super.emit('*', eventRecord);
}
replay() {
this._events.forEach(({ event, args }) => {
super.emit(event, ...args);
});
}
}
包装 emit 方法进行调试
javascript复制const originalEmit = EventEmitter.prototype.emit;
EventEmitter.prototype.emit = function(event, ...args) {
console.log(`[${new Date().toISOString()}] Emitting: ${event}`, args);
return originalEmit.call(this, event, ...args);
};
使用 Async Hooks 追踪事件上下文
javascript复制const async_hooks = require('async_hooks');
const hooks = async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
if (type === 'EMITTER') {
// 跟踪事件发射的异步上下文
}
}
});
hooks.enable();
监控 EventEmitter 的关键指标:
emitter.listenerCount('event')性能监控示例
javascript复制const eventStats = new Map();
function wrapEmitter(emitter) {
const originalEmit = emitter.emit.bind(emitter);
emitter.emit = function(event, ...args) {
// 统计事件频率
const count = eventStats.get(event) || 0;
eventStats.set(event, count + 1);
// 测量监听器执行时间
const listeners = emitter.listeners(event);
if (listeners.length > 0) {
const start = process.hrtime.bigint();
const result = originalEmit(event, ...args);
const duration = Number(process.hrtime.bigint() - start) / 1e6;
console.log(`Event ${event} took ${duration.toFixed(2)}ms`);
return result;
}
return originalEmit(event, ...args);
};
}
内存泄漏问题
问题场景:
javascript复制server.on('connection', (socket) => {
socket.on('data', () => {
// 处理数据
});
});
每次新连接都会添加监听器,但从不移除,导致内存泄漏。
解决方案:
javascript复制server.on('connection', (socket) => {
const dataHandler = () => { /*...*/ };
socket.on('data', dataHandler);
socket.on('close', () => {
socket.removeListener('data', dataHandler);
});
});
事件循环过载
问题场景:
高频事件(如鼠标移动、传感器数据)导致事件循环被阻塞。
解决方案:
javascript复制let lastEmit = 0;
const throttleMs = 100;
function emitThrottled(event, data) {
const now = Date.now();
if (now - lastEmit >= throttleMs) {
emitter.emit(event, data);
lastEmit = now;
}
}
单元测试事件发射
javascript复制const assert = require('assert');
const { EventEmitter } = require('events');
describe('EventEmitter', () => {
it('should emit events', (done) => {
const emitter = new EventEmitter();
emitter.once('test', (value) => {
assert.strictEqual(value, 42);
done();
});
emitter.emit('test', 42);
});
it('should handle async events', async () => {
const emitter = new EventEmitter();
const promise = new Promise(resolve => {
emitter.once('async', resolve);
});
setTimeout(() => {
emitter.emit('async', 'done');
}, 100);
const result = await promise;
assert.strictEqual(result, 'done');
});
});
集成测试建议
将 EventEmitter 转换为 Async Iterator
javascript复制function eventToAsyncIterator(emitter, eventName, endEvent) {
const queue = [];
let resolve;
emitter.on(eventName, (data) => {
if (resolve) {
resolve({ value: data, done: false });
resolve = null;
} else {
queue.push(data);
}
});
if (endEvent) {
emitter.once(endEvent, () => {
if (resolve) {
resolve({ done: true });
}
});
}
return {
[Symbol.asyncIterator]() {
return this;
},
next() {
if (queue.length > 0) {
return Promise.resolve({
value: queue.shift(),
done: false
});
}
return new Promise(r => resolve = r);
}
};
}
// 使用示例
async function processEvents() {
const iterator = eventToAsyncIterator(emitter, 'data', 'end');
for await (const data of iterator) {
console.log('Received:', data);
}
}
与 RxJS 互操作
javascript复制const { fromEvent } = require('rxjs');
const { filter, map } = require('rxjs/operators');
// 将EventEmitter转换为Observable
const data$ = fromEvent(emitter, 'data')
.pipe(
filter(data => data.value > 0.5),
map(data => ({ ...data, processed: true }))
);
// 订阅Observable
const subscription = data$.subscribe({
next: data => console.log('Processed:', data),
error: err => console.error('Error:', err),
complete: () => console.log('Completed')
});
// 取消订阅
setTimeout(() => subscription.unsubscribe(), 5000);
基于 EventEmitter 的简单服务总线
javascript复制class ServiceBus {
constructor() {
this._emitter = new EventEmitter();
this._services = new Map();
}
registerService(name, handler) {
this._services.set(name, handler);
this._emitter.on(name, async (payload) => {
try {
const result = await handler(payload);
this._emitter.emit(`${name}.success`, result);
} catch (error) {
this._emitter.emit(`${name}.error`, error);
}
});
}
callService(name, payload) {
return new Promise((resolve, reject) => {
this._emitter.once(`${name}.success`, resolve);
this._emitter.once(`${name}.error`, reject);
this._emitter.emit(name, payload);
});
}
}
事件驱动的状态机
javascript复制class StateMachine extends EventEmitter {
constructor(states, initialState) {
super();
this._states = states;
this._state = initialState;
}
transition(event) {
const currentState = this._states[this._state];
const transition = currentState.transitions[event];
if (!transition) {
this.emit('error', new Error(`Invalid transition: ${this._state} -> ${event}`));
return;
}
const prevState = this._state;
this._state = transition.to;
// 调用退出动作
if (currentState.exit) currentState.exit();
this.emit('transition', {
from: prevState,
to: this._state,
event
});
// 调用进入动作
if (transition.action) transition.action();
// 调用新状态的enter动作
if (this._states[this._state].enter) {
this._states[this._state].enter();
}
}
}
当事件名或数据来自不可信源时:
javascript复制const ALLOWED_EVENTS = new Set(['data', 'update', 'status']);
function safeEmit(emitter, event, data) {
if (!ALLOWED_EVENTS.has(event)) {
throw new Error(`Disallowed event: ${event}`);
}
// 深度克隆数据避免引用问题
const safeData = JSON.parse(JSON.stringify(data));
emitter.emit(event, safeData);
}
避免通过事件传递敏感信息:
javascript复制// 不推荐
emitter.emit('user-login', {
username: 'admin',
password: 'secret' // 敏感信息
});
// 推荐
emitter.emit('user-login', {
userId: 123,
timestamp: Date.now()
});
使用批量处理
javascript复制let batch = [];
const BATCH_SIZE = 100;
const BATCH_FLUSH_MS = 1000;
function emitBatch(event, item) {
batch.push(item);
if (batch.length >= BATCH_SIZE) {
flushBatch();
return;
}
if (!batchTimeout) {
batchTimeout = setTimeout(flushBatch, BATCH_FLUSH_MS);
}
}
function flushBatch() {
if (batch.length > 0) {
emitter.emit(event, [...batch]);
batch = [];
}
clearTimeout(batchTimeout);
batchTimeout = null;
}
使用 Performance Hook 测量
javascript复制const { PerformanceObserver, performance } = require('perf_hooks');
const obs = new PerformanceObserver((items) => {
console.log(items.getEntries());
});
obs.observe({ entryTypes: ['function'] });
// 包装监听器进行测量
function wrapListener(listener) {
return performance.timerify(function(...args) {
return listener.apply(this, args);
});
}
emitter.on('data', wrapListener((data) => {
// 处理逻辑
}));
请求生命周期事件
javascript复制const app = require('express')();
app.use((req, res, next) => {
const start = Date.now();
req.on('end', () => {
const duration = Date.now() - start;
app.emit('request-completed', {
method: req.method,
path: req.path,
duration
});
});
next();
});
// 监控所有请求
app.on('request-completed', (metrics) => {
console.log(`Request ${metrics.method} ${metrics.path} took ${metrics.duration}ms`);
});
Mongoose 集成示例
javascript复制const mongoose = require('mongoose');
// 监听所有查询
mongoose.set('debug', (collection, method, query, doc) => {
emitter.emit('mongoose-query', {
collection,
method,
query,
doc
});
});
// 分析慢查询
emitter.on('mongoose-query', ({ collection, method, query }) => {
const start = Date.now();
process.nextTick(() => {
const duration = Date.now() - start;
if (duration > 100) {
console.warn(`Slow query (${duration}ms): ${collection}.${method}`, query);
}
});
});
EventTarget 标准化
javascript复制// Node.js 15+ 支持
const { EventTarget, Event } = require('events');
const target = new EventTarget();
target.addEventListener('foo', (event) => {
console.log('foo event happened!', event);
});
const event = new Event('foo');
target.dispatchEvent(event);
| 方案 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| EventEmitter | 单进程应用 | 简单直接,Node.js 内置 | 不适合分布式 |
| RxJS | 复杂事件流 | 强大的操作符,组合能力 | 学习曲线陡峭 |
| Redux | 状态管理 | 可预测的状态变更 | 样板代码多 |
| Socket.IO | 实时通信 | 跨平台,自动重连 | 需要网络连接 |
使用 debug 模块
javascript复制const debug = require('debug')('events');
const originalEmit = EventEmitter.prototype.emit;
EventEmitter.prototype.emit = function(event, ...args) {
debug(`Emitting ${event} with %o`, args);
return originalEmit.call(this, event, ...args);
};
启动调试
bash复制DEBUG=events node app.js
使用 heapdump
javascript复制const heapdump = require('heapdump');
emitter.on('leak-suspect', () => {
heapdump.writeSnapshot((err, filename) => {
console.log(`Heap dump written to ${filename}`);
});
});
基准测试脚本
javascript复制const Benchmark = require('benchmark');
const suite = new Benchmark.Suite();
const emitter = new EventEmitter();
emitter.on('bench', () => {});
suite
.add('emit with one listener', () => {
emitter.emit('bench');
})
.add('emit with ten listeners', () => {
for (let i = 0; i < 10; i++) {
emitter.on('bench', () => {});
}
emitter.emit('bench');
})
.on('cycle', (event) => {
console.log(String(event.target));
})
.run();
继承 vs 组合
javascript复制// 继承模式
class Inherited extends EventEmitter {
constructor() { super(); }
}
// 组合模式
class Composed {
constructor() {
this._emitter = new EventEmitter();
}
on() { return this._emitter.on(...arguments); }
emit() { return this._emitter.emit(...arguments); }
}
// 测试两者的性能差异
命名规范
内存管理
错误处理
性能优化
测试策略
架构设计
在实际项目中,我通常会创建一个中央事件总线来管理核心事件流,同时为每个模块创建独立的事件发射器处理内部通信。这种分层设计既保持了模块间的解耦,又避免了全局事件泛滥的问题。