事件发射器模式
事件发射器模式
事件发射器模式是Node.js中处理异步事件的核心机制之一。它允许对象发布命名事件,其他对象可以监听这些事件并做出响应。这种模式解耦了事件触发器和事件处理器之间的关系,使得代码更加模块化和可维护。
事件发射器的工作原理
Node.js中的events
模块提供了EventEmitter
类,它是实现事件发射器模式的基础。任何继承自EventEmitter
的对象都可以成为事件发射器:
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
事件发射器主要有两个核心方法:
emit(eventName[, ...args])
:触发指定事件on(eventName, listener)
:为指定事件添加监听器
基本用法示例
下面是一个简单的事件发射器使用示例:
const EventEmitter = require('events');
class Door extends EventEmitter {
open() {
console.log('门开了');
this.emit('open', new Date());
}
close() {
console.log('门关了');
this.emit('close', new Date());
}
}
const frontDoor = new Door();
// 添加事件监听器
frontDoor.on('open', (time) => {
console.log(`有人在 ${time} 打开了门`);
});
frontDoor.on('close', (time) => {
console.log(`有人在 ${time} 关上了门`);
});
// 触发事件
frontDoor.open();
setTimeout(() => frontDoor.close(), 1000);
事件发射器的特性
同步执行监听器
事件发射器会同步调用所有监听器,按照它们被注册的顺序执行:
const emitter = new EventEmitter();
emitter.on('event', () => console.log('第一个监听器'));
emitter.on('event', () => console.log('第二个监听器'));
emitter.emit('event');
// 输出:
// 第一个监听器
// 第二个监听器
一次性监听器
使用once()
方法可以注册只触发一次的监听器:
const emitter = new EventEmitter();
emitter.once('login', (user) => {
console.log(`${user} 首次登录`);
});
emitter.emit('login', '张三'); // 会触发
emitter.emit('login', '张三'); // 不会触发
错误事件处理
EventEmitter
对error
事件有特殊处理。如果没有为error
事件注册监听器,当触发error
事件时,Node.js会抛出异常并退出进程:
const emitter = new EventEmitter();
// 必须处理error事件
emitter.on('error', (err) => {
console.error('发生错误:', err.message);
});
emitter.emit('error', new Error('出错了!'));
高级用法
获取监听器信息
const emitter = new EventEmitter();
const listener = () => console.log('事件触发');
emitter.on('event', listener);
console.log(emitter.listenerCount('event')); // 1
console.log(emitter.eventNames()); // ['event']
移除监听器
const emitter = new EventEmitter();
function listener() {
console.log('事件触发');
emitter.removeListener('event', listener);
}
emitter.on('event', listener);
emitter.emit('event'); // 会触发
emitter.emit('event'); // 不会触发
设置最大监听器数量
默认情况下,每个事件最多可以有10个监听器。超过这个数量会输出警告:
const emitter = new EventEmitter();
emitter.setMaxListeners(20); // 增加到20个
实际应用场景
HTTP服务器
Node.js的HTTP服务器就是基于事件发射器构建的:
const http = require('http');
const server = http.createServer();
server.on('request', (req, res) => {
res.end('Hello World');
});
server.listen(3000);
流处理
Node.js的流也是事件发射器的实例:
const fs = require('fs');
const readStream = fs.createReadStream('file.txt');
readStream.on('data', (chunk) => {
console.log(`接收到 ${chunk.length} 字节数据`);
});
readStream.on('end', () => {
console.log('没有更多数据了');
});
自定义事件总线
可以创建一个全局事件总线来实现不同模块间的通信:
// eventBus.js
const EventEmitter = require('events');
module.exports = new EventEmitter();
// moduleA.js
const bus = require('./eventBus');
bus.emit('data-ready', { data: 'some data' });
// moduleB.js
const bus = require('./eventBus');
bus.on('data-ready', (data) => {
console.log('接收到数据:', data);
});
性能考虑
避免内存泄漏
忘记移除监听器可能导致内存泄漏:
class Resource {
constructor() {
this.emitter = new EventEmitter();
this.emitter.on('data', this.handleData);
}
handleData = (data) => {
console.log(data);
}
destroy() {
// 必须移除监听器
this.emitter.removeListener('data', this.handleData);
}
}
批量操作优化
当需要触发多个事件时,可以考虑批量处理:
const emitter = new EventEmitter();
// 不推荐
for (let i = 0; i < 1000; i++) {
emitter.emit('data', i);
}
// 推荐
const batch = [];
for (let i = 0; i < 1000; i++) {
batch.push(i);
}
emitter.emit('batch-data', batch);
与其他模式的比较
与回调模式比较
事件发射器模式比简单的回调模式更灵活:
// 回调模式
function fetchData(callback) {
// ...获取数据
callback(data);
}
// 事件发射器模式
class DataFetcher extends EventEmitter {
fetch() {
// ...获取数据
this.emit('data', data);
this.emit('end');
}
}
与Promise/async-await比较
事件发射器适合处理多个离散事件,而Promise更适合单次异步操作:
// Promise - 单次操作
function getUser(id) {
return new Promise((resolve, reject) => {
// ...获取用户
resolve(user);
});
}
// 事件发射器 - 持续事件
class UserTracker extends EventEmitter {
track(userId) {
setInterval(() => {
const status = checkUserStatus(userId);
this.emit('status-change', status);
}, 1000);
}
}
扩展EventEmitter
可以通过继承来扩展EventEmitter的功能:
class EnhancedEmitter extends EventEmitter {
emitWithLog(event, ...args) {
console.log(`触发事件: ${event}`);
return this.emit(event, ...args);
}
waitFor(event, timeout = 5000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error('等待事件超时'));
}, timeout);
this.once(event, (...args) => {
clearTimeout(timer);
resolve(args);
});
});
}
}
// 使用示例
const emitter = new EnhancedEmitter();
emitter.emitWithLog('test', 1, 2, 3);
(async () => {
try {
const result = await emitter.waitFor('data');
console.log(result);
} catch (err) {
console.error(err);
}
})();
浏览器中的事件发射器
虽然Node.js的events
模块是服务器端的,但类似的概念也存在于浏览器中:
// 自定义事件发射器类
class BrowserEventEmitter {
constructor() {
this.listeners = {};
}
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
emit(event, ...args) {
if (this.listeners[event]) {
this.listeners[event].forEach(cb => cb(...args));
}
}
}
// 使用示例
const emitter = new BrowserEventEmitter();
emitter.on('click', (x, y) => {
console.log(`点击位置: ${x}, ${y}`);
});
emitter.emit('click', 100, 200);
测试事件发射器
测试事件发射器时,可以使用模拟或间谍函数:
const assert = require('assert');
const EventEmitter = require('events');
describe('EventEmitter测试', () => {
it('应该触发事件并调用监听器', () => {
const emitter = new EventEmitter();
let called = false;
emitter.on('test', () => {
called = true;
});
emitter.emit('test');
assert.strictEqual(called, true);
});
it('应该传递正确的参数', (done) => {
const emitter = new EventEmitter();
emitter.on('data', (a, b) => {
try {
assert.strictEqual(a, 1);
assert.strictEqual(b, 2);
done();
} catch (err) {
done(err);
}
});
emitter.emit('data', 1, 2);
});
});
常见问题与解决方案
事件监听器过多
当事件监听器数量过多时,可以考虑使用事件聚合:
const emitter = new EventEmitter();
// 原始方式 - 多个独立事件
emitter.on('user-added', handleUserAdded);
emitter.on('user-updated', handleUserUpdated);
// 改进方式 - 聚合事件
emitter.on('user-change', (event) => {
switch (event.type) {
case 'added':
handleUserAdded(event.user);
break;
case 'updated':
handleUserUpdated(event.user);
break;
}
});
事件顺序依赖
当事件有顺序依赖时,可以使用异步控制流库:
const { AsyncSeriesWaterfallHook } = require('tapable');
const hook = new AsyncSeriesWaterfallHook(['data']);
hook.tapAsync('第一步', (data, callback) => {
setTimeout(() => {
data.step1 = '完成';
callback(null, data);
}, 100);
});
hook.tapAsync('第二步', (data, callback) => {
setTimeout(() => {
data.step2 = '完成';
callback(null, data);
}, 50);
});
hook.callAsync({}, (err, result) => {
console.log(result);
});
跨进程事件
对于跨进程的事件通信,可以使用消息队列或专门的库:
// 使用Redis实现跨进程事件总线
const redis = require('redis');
const subscriber = redis.createClient();
const publisher = redis.createClient();
subscriber.on('message', (channel, message) => {
console.log(`接收到消息: ${message} 来自频道: ${channel}`);
});
subscriber.subscribe('notifications');
// 在另一个进程中
publisher.publish('notifications', 'Hello from Process B');
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:回调地狱问题与解决方案
下一篇:发布/订阅模式