Nodejs-HardCore: 玩转 EventEmitter 指南

基础用法

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 应用至关重要。在复杂系统中,合理选择事件管理模式可以显著提升代码质量和系统可靠性

相关推荐
DYS_房东的猫2 小时前
《 C++ 零基础入门教程》第5章:智能指针与 RAII —— 让内存管理自动化
开发语言·c++·自动化
零度@2 小时前
Java 消息中间件 - 云原生多租户:Pulsar 保姆级全解2026
java·开发语言·云原生
jghhh012 小时前
基于MATLAB的分块压缩感知程序实现与解析
开发语言·算法·matlab
枫叶丹42 小时前
【Qt开发】Qt系统(六)-> Qt 线程安全
c语言·开发语言·数据库·c++·qt·安全
你怎么知道我是队长2 小时前
C语言---错误处理
c语言·开发语言
信奥胡老师2 小时前
P14917 [GESP202512 五级] 数字移动
开发语言·数据结构·c++·学习·算法
天若有情6732 小时前
用 Python 爬取电商商品数据:从入门到反爬破解
开发语言·python
txinyu的博客2 小时前
结合STL,服务器项目解析vetcor map unordered_map
开发语言·c++
北京地铁1号线2 小时前
1.1 文档解析:PDF/Word/HTML的结构化提取
开发语言·知识图谱·文档解析