基础用法
1 ) 从 EventEmitter 继承
Node.js 的事件驱动架构核心是 EventEmitter 类,它提供了事件监听和触发的功能
当需要创建自定义事件对象时,可通过继承 EventEmitter 实现
javascript
const EventEmitter = require('events');
const util = require('util');
// 音乐播放器类
function MusicPlayer() {
EventEmitter.call(this);
this.track = null;
this.playing = false;
}
// 使用 util.inherits 实现继承
util.inherits(MusicPlayer, EventEmitter);
// 创建实例
const player = new MusicPlayer();
// 添加播放事件监听
player.on('play', function(track) {
this.playing = true;
this.track = track;
console.log(Now playing: ${track});
});
// 添加暂停事件监听
player.on('pause', function() {
this.playing = false;
console.log(Paused: ${this.track});
});
// 触发事件
player.emit('play', 'Stairway to Heaven');
player.emit('pause');
// 输出:
// Now playing: Stairway to Heaven
// Paused: Stairway to Heaven
util.inherits() 方法将 EventEmitter 的原型方法复制到 MusicPlayer 的原型上,使所有实例都能使用 on、emit 等方法
管理多个监听器
javascript
// 添加多个监听器
player.on('volume-up', () => console.log('Volume increased'));
player.on('volume-up', () => console.log('Another volume handler'));
player.emit('volume-up');
// 输出:
// Volume increased
// Another volume handler
// 移除单个监听器
const volumeHandler = () => console.log('Will be removed');
player.on('volume-down', volumeHandler);
player.removeListener('volume-down', volumeHandler);
// 移除所有监听器
player.removeAllListeners('volume-up');
2 )混合 EventEmitter
除了继承,还可以通过混合方式将 EventEmitter 功能添加到现有类中:
javascript
function createMixin(target) {
const emitter = new EventEmitter();
// 将核心方法复制到目标对象
target.on = emitter.on.bind(emitter);
target.emit = emitter.emit.bind(emitter);
target.removeListener = emitter.removeListener.bind(emitter);
return target;
}
// 普通对象
const logger = {
log: function(message) {
console.log(message);
}
};
// 添加事件能力
createMixin(logger);
// 使用事件
logger.on('log-event', (msg) => console.log(Event: ${msg}));
logger.emit('log-event', 'Logging with events!');
或参考下例
ts
function createEventEmitterMixin() {
const emitter = new EventEmitter();
return {
on: emitter.on.bind(emitter),
once: emitter.once.bind(emitter),
emit: emitter.emit.bind(emitter),
removeListener: emitter.removeListener.bind(emitter)
};
}
// 在类中使用
class AudioPlayer {
constructor() {
Object.assign(this, createEventEmitterMixin());
}
play(track) {
this.emit('play', track);
}
}
// Connect 中的 util.merge 方法 (类似实现)
function mergeEvents(target) {
const emitter = new EventEmitter();
['on', 'once', 'emit', 'removeListener'].forEach(method => {
target[method] = emitter[method].bind(emitter);
});
return target;
}
混合前
混合过程
混合后
目标对象
普通功能
创建EventEmitter实例
复制方法到目标对象
具有事件功能的对象
添加监听器
触发事件
移除监听器
3 ) 异常处理
3.1 管理异常
EventEmitter 对 error 事件有特殊处理,未处理时会导致进程退出:
javascript
const fs = require('fs');
const readable = fs.createReadStream('nonexistent.txt');
// 必须处理error事件
readable.on('error', (err) => {
console.error(File error: ${err.message});
});
// 自定义事件错误处理
player.on('error', (err) => {
console.error(Player error: ${err.message});
});
// 故意触发错误
player.emit('error', new Error('Track not found'));
有
无
是
否
事件触发
有错误监听器?
执行错误处理
是同步错误?
抛出异常
触发'uncaughtException'
进程终止
3.2 通过 domains 管理异常
虽然 domain 模块已弃用,但了解其机制有助于理解错误处理模式:
javascript
const domain = require('domain');
const d = domain.create();
// 处理域内错误
d.on('error', (err) => {
console.error(Domain caught: ${err.message});
});
// 在域中运行易错代码
d.run(() => {
// 模拟异步操作
process.nextTick(() => {
throw new Error('Async error in domain');
});
});
注意这种方案已经弃用
现代替代方案:
- 使用
try/catch处理同步错误 - 使用
Promise.catch()处理异步错误 - 使用
async/await配合try/catch
参考下例
ts
// 现代替代方案:使用 async_hooks 或 process.on('uncaughtException')
process.on('uncaughtException', (err) => {
console.error('未捕获的异常:', err);
// 执行清理操作
process.exit(1);
});
// 使用 try/catch 处理同步错误
try {
dangerousOperation();
} catch (err) {
player.emit('error', err);
}
4 ) 高级模式
4.1 反射
javascript
// 监听新监听器添加事件
player.on('newListener', (eventName, listener) => {
console.log(New listener for ${eventName} added);
});
// 查询监听器
console.log(player.listeners('play'));
console.log(player.listenerCount('play'));
// 自动触发事件
player.on('newListener', (eventName) => {
if (eventName === 'progress') {
setInterval(() => {
player.emit('progress', Math.random());
}, 1000);
}
});
player.on('progress', (pct) => {
console.log(Progress: ${Math.round(pct * 100)}%);
});
再参考下例
ts
const tracker = new EventEmitter();
// 监听新监听器的添加
tracker.on('newListener', (event, listener) => {
console.log(`New listener added for ${event}`);
if (event === 'progress') {
// 自动开始追踪
console.log('Starting progress tracking...');
setInterval(() => tracker.emit('progress', Math.random() * 100), 1000);
}
});
// 监听监听器的移除
tracker.on('removeListener', (event, listener) => {
console.log(`Listener removed from ${event}`);
});
// 添加监听器
const progressHandler = (value) => console.log(`Progress: ${value.toFixed(1)}%`);
tracker.on('progress', progressHandler);
// 5秒后移除监听器
setTimeout(() => {
tracker.removeListener('progress', progressHandler);
}, 5000);
探索 EventEmitter
1 ) Express.js 中的 EventEmitter:
javascript
const express = require('express');
const app = express();
// Express应用本身就是EventEmitter
app.on('boot', () => {
console.log('Application booting');
});
app.listen(3000, () => {
app.emit('boot');
});
// 中间件中访问
app.use((req, res, next) => {
req.app.emit('request-start'); // 访问应用实例
next();
});
2 ) Redis 客户端事件:
javascript
const redis = require('redis');
const client = redis.createClient();
// 监听连接事件
client.on('connect', () => {
console.log('Redis connected');
});
// 监听错误事件
client.on('error', (err) => {
console.error('Redis error:', err);
});
3 ) 组织事件名称
javascript
// events.js
module.exports = {
PLAY: 'play',
PAUSE: 'pause',
STOP: 'stop',
ERROR: 'error',
PROGRESS: 'progress'
};
// player.js
const events = require('./events');
player.on(events.PLAY, (track) => {
// 播放逻辑
});
player.emit(events.PROGRESS, 0.75);
// 优点:
// 1. 避免拼写错误
// 2. 集中管理事件
// 3. IDE自动补全支持
单进程
多进程
是
否
浏览器
实时通知
需求分析
通信范围
EventEmitter
是否需要持久化
RabbitMQ
ZeroMQ
js-signals
Redis Pub/Sub
第三方模块及扩展
EventEmitter 的替代方案
1 ) RabbitMQ (AMQP):
javascript
const amqp = require('amqplib');
async function main() {
const conn = await amqp.connect('amqp://localhost');
const channel = await conn.createChannel();
const queue = 'tasks';
await channel.assertQueue(queue);
// 发布消息
channel.sendToQueue(queue, Buffer.from('Task data'));
// 消费消息
channel.consume(queue, (msg) => {
console.log('Received:', msg.content.toString());
channel.ack(msg);
});
}
2 ) Redis Pub/Sub:
javascript
const redis = require('redis');
const publisher = redis.createClient();
const subscriber = redis.createClient();
// 订阅频道
subscriber.subscribe('updates');
subscriber.on('message', (channel, message) => {
console.log(Message on ${channel}: ${message});
});
// 发布消息
publisher.publish('updates', 'New data available');
// 关闭连接
process.on('exit', () => {
publisher.quit();
subscriber.quit();
});
3 ) ZeroMQ:
javascript
const zmq = require('zeromq');
const publisher = zmq.socket('pub');
publisher.bindSync('tcp://*:6000');
console.log('Publisher bound to port 6000');
setInterval(() => {
publisher.send(['topic', 'message data']);
}, 1000);
4 ) Signal 模式:
javascript
const Signal = require('js-signals');
// 创建信号
const taskCompleted = new Signal();
// 添加监听器
taskCompleted.add((result) => {
console.log(Task completed with: ${result});
});
// 分发信号
taskCompleted.dispatch('success');
EventEmitter
Node.js核心
RabbitMQ AMQP
Redis Pub/Sub
ZeroMQ
JS-Signals
内置支持
消息队列
轻量级发布订阅
高性能消息传递
浏览器环境友好
总结
1 ) 核心继承模式
- 使用 util.inherits() 或 ES6 class extends 创建事件驱动对象
- 通过混合模式为现有对象添加事件能力
2 ) 异常处理实践:
- 必须处理 error 事件防止进程崩溃
- 使用现代错误处理替代已弃用的 domain 模块
3 ) 高级事件技术:
- 利用 newListener 事件实现反射
- 在Express等框架中复用EventEmitter
- 使用常量对象管理事件名称
4 ) 替代方案选择:
- 分布式系统:RabbitMQ/AMQP
- 轻量级发布订阅:Redis
- 高性能IPC:ZeroMQ
- 浏览器兼容:js-signals
EventEmitter 是 Node.js 异步编程的核心模式,掌握其原理和各种应用场景对于构建高效、可维护的 Node.js 应用至关重要。在复杂系统中,合理选择事件管理模式可以显著提升代码质量和系统可靠性