Node.js: 事件系统 EventEmitter

最后更新:2026-08-26

Alice 是一名后端工程师,正在开发一个电商订单系统。最初她让订单创建函数直接调用发邮件、更新库存、记日志三个模块,结果每次新增功能都要修改订单核心代码,系统越来越脆弱。后来她用 EventEmitter 重构:订单模块只负责触发 order:created 事件,各服务独立监听,互不干扰。新增"短信通知"只需添加一个监听器,核心代码零改动。这种发布-订阅架构让系统维护成本降低了 60%。

1. 你将学到



2. EventEmitter 核心概念

EventEmitter 是 Node.js 事件驱动的基石类,位于 events 模块。它维护一个事件名到监听器函数数组的映射,当 emit() 触发事件时,所有已注册的监听器按注册顺序同步执行。

100%
flowchart LR
    subgraph Emitter["EventEmitter(发布者)"]
        E1[emit - order:created]
    end
    subgraph Listeners["监听器(订阅者)"]
        L1[监听器1:发送邮件]
        L2[监听器2:更新库存]
        L3[监听器3:记录日志]
    end
    E1 --> L1
    E1 --> L2
    E1 --> L3
    style Emitter fill:#e1f5fe
    style Listeners fill:#f3e5f5
JAVASCRIPT
const EventEmitter = require('events');

const emitter = new EventEmitter();

emitter.on('greet', (name) => {
  console.log(`Hello, ${name}!`);
});

emitter.emit('greet', 'Alice');
TEXT 📖 仅展示
Hello, Alice!


3. EventEmitter 常用方法速查

方法 说明 返回值
on(event, listener) 注册监听器,可重复注册 EventEmitter 实例
once(event, listener) 注册一次性监听器,触发后自动移除 EventEmitter 实例
emit(event, ...args) 触发事件,传递参数 true 有监听器 / false 无监听器
off(event, listener) 移除指定监听器 EventEmitter 实例
removeListener(event, listener) off(),旧 API EventEmitter 实例
removeAllListeners([event]) 移除所有或指定事件的监听器 EventEmitter 实例
prependListener(event, listener) 将监听器添加到队列头部 EventEmitter 实例
prependOnceListener(event, listener) 头部添加一次性监听器 EventEmitter 实例
listeners(event) 返回事件监听器数组 Function[]
listenerCount(event) 返回监听器数量 number
setMaxListeners(n) 设置单个事件最大监听器数 EventEmitter 实例
getMaxListeners() 获取最大监听器数 number
eventNames() 返回所有已注册事件名 `(string
rawListeners(event) 返回包含 once 标记的原始监听器 Function[]

▶ 示例:基本 on/emit/off 使用

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

function onOrderCreated(order) {
  console.log(`[Listener] Order created: #${order.id}`);
}

emitter.on('order:created', onOrderCreated);

emitter.emit('order:created', { id: 1001, product: 'Laptop', qty: 2 });

emitter.off('order:created', onOrderCreated);

const hasListeners = emitter.emit('order:created', { id: 1002, product: 'Mouse', qty: 5 });
console.log('Has listeners:', hasListeners);
▶ 试一试
TEXT 📖 仅展示
[Listener] Order created: #1001
Has listeners: false


4. on vs once vs prependListener 对比

特性 on() once() prependListener()
触发次数 每次都触发 仅触发一次后自动移除 每次都触发
注册位置 队列尾部 队列尾部 队列头部(优先执行)
典型场景 持续监听消息 初始化只执行一次 高优先级处理(如日志)
自动移除
对应一次性版本 once() prependOnceListener()

▶ 示例:once 只触发一次

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.once('server:ready', (port) => {
  console.log(`Server initialized on port ${port}`);
});

emitter.emit('server:ready', 3000);
emitter.emit('server:ready', 3001);
console.log('Second emit had no effect');
▶ 试一试
TEXT 📖 仅展示
Server initialized on port 3000
Second emit had no effect

▶ 示例:prependListener 优先执行

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.on('data', () => console.log('Normal listener'));
emitter.prependListener('data', () => console.log('Priority listener'));

emitter.emit('data');
▶ 试一试
TEXT 📖 仅展示
Priority listener
Normal listener


5. 事件监听器管理

Node.js 默认单个事件最多 10 个监听器,超过会打印内存泄漏警告。这并非硬限制,而是提醒开发者检查是否忘记移除监听器。

▶ 示例:listenerCount 与内存泄漏警告

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

for (let i = 0; i < 12; i++) {
  emitter.on('log', () => console.log(`Listener ${i}`));
}

console.log('Listener count:', emitter.listenerCount('log'));
console.log('Max listeners:', emitter.getMaxListeners());
▶ 试一试
TEXT 📖 仅展示
(node:1234) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 12 log listeners added. Use emitter.setMaxListeners() to increase limit
Listener count: 12
Max listeners: 10

▶ 示例:setMaxListeners 调整上限

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.setMaxListeners(20);

for (let i = 0; i < 15; i++) {
  emitter.on('task', () => {});
}

console.log('No warning: max set to', emitter.getMaxListeners());
console.log('Listener count:', emitter.listenerCount('task'));
▶ 试一试
TEXT 📖 仅展示
No warning: max set to 20
Listener count: 15

▶ 示例:removeAllListeners 清理

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.on('tick', () => console.log('tick A'));
emitter.on('tick', () => console.log('tick B'));
emitter.on('tock', () => console.log('tock A'));

emitter.removeAllListeners('tick');

console.log('tick listeners:', emitter.listenerCount('tick'));
console.log('tock listeners:', emitter.listenerCount('tock'));
▶ 试一试
TEXT 📖 仅展示
tick listeners: 0
tock listeners: 1


6. 自定义事件类

实际开发中,通常继承 EventEmitter 创建具有业务语义的事件类,而非直接使用 EventEmitter 实例。

▶ 示例:继承 EventEmitter 创建订单事件类

JAVASCRIPT
const EventEmitter = require('events');

class OrderEmitter extends EventEmitter {
  create(order) {
    this.emit('order:created', order);
  }

  cancel(orderId) {
    this.emit('order:cancelled', { orderId, cancelledAt: new Date().toISOString() });
  }

  ship(orderId, trackingNo) {
    this.emit('order:shipped', { orderId, trackingNo });
  }
}

const orderEvents = new OrderEmitter();

orderEvents.on('order:created', (order) => {
  console.log(`[Email] Confirmation for order #${order.id}`);
});

orderEvents.on('order:created', (order) => {
  console.log(`[Inventory] Deduct ${order.qty}x ${order.product}`);
});

orderEvents.on('order:cancelled', ({ orderId }) => {
  console.log(`[Refund] Processing refund for #${orderId}`);
});

orderEvents.create({ id: 2001, product: 'Headphones', qty: 3 });
orderEvents.cancel(2001);
▶ 试一试
TEXT 📖 仅展示
[Email] Confirmation for order #2001
[Inventory] Deduct 3x Headphones
[Refund] Processing refund for #2001


7. error 事件与错误处理

EventEmitter 有特殊的 error 事件:如果触发了 error 事件但没有任何监听器,Node.js 会抛出异常并终止进程。这是 EventEmitter 中最重要的安全规则。

▶ 示例:未监听 error 导致进程崩溃

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.emit('error', new Error('Database connection failed'));
▶ 试一试
TEXT 📖 仅展示
events.js:291
      throw er; // Unhandled 'error' event
      ^
Error: Database connection failed
    at Object.<anonymous> (app.js:4:20)

▶ 示例:正确处理 error 事件

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.on('error', (err) => {
  console.error(`[Error Handler] ${err.message}`);
});

emitter.emit('error', new Error('Database connection failed'));
console.log('Process continues running');
▶ 试一试
TEXT 📖 仅展示
[Error Handler] Database connection failed
Process continues running


8. 事件驱动 vs 回调 vs Promise 对比

特性 事件驱动(EventEmitter) 回调(Callback) Promise / async-await
通信模式 一对多(发布-订阅) 一对一 一对一
触发次数 可多次触发 仅触发一次 仅解决一次(resolve 一次)
典型场景 消息广播、状态变更 I/O 操作完成 异步操作的最终结果
耦合度 低(发布者不认识订阅者) 高(调用者必须知道回调) 中(链式或 await)
可取消 可 off() 移除 不可取消 不可取消(可忽略)
内置支持 events 模块 全局约定 语言内置
错误处理 error 事件 错误优先回调 .catch() / try-catch


9. Node.js 内置使用 EventEmitter 的模块

Node.js 大量内置模块继承自 EventEmitter,几乎所有流、服务器、进程对象都是事件发射器。

模块/对象 继承 EventEmitter 常用事件
net.Server connection, close, error
http.Server request, connection, close
stream.Readable data, end, error, close
stream.Writable drain, finish, error, close
net.Socket data, connect, end, error
process exit, uncaughtException, SIGINT
child_process.ChildProcess exit, message, error, close
fs.watch() 返回 EventEmitter change, error

▶ 示例:http.Server 使用 EventEmitter

JAVASCRIPT
const http = require('http');

const server = http.createServer();

server.on('request', (req, res) => {
  console.log(`[Request] ${req.method} ${req.url}`);
  res.end('OK');
});

server.on('connection', (socket) => {
  console.log(`[Connection] New client from ${socket.remoteAddress}`);
});

server.listen(3000, () => {
  console.log('Server listening on port 3000');
});
▶ 试一试
TEXT 📖 仅展示
Server listening on port 3000
[Connection] New client from ::ffff:127.0.0.1
[Request] GET /

▶ 示例:Readable Stream 使用 EventEmitter

JAVASCRIPT
const { Readable } = require('stream');

const readable = Readable.from(['Hello', ' ', 'World']);

readable.on('data', (chunk) => {
  console.log(`[Data] Received: "${chunk}"`);
});

readable.on('end', () => {
  console.log('[End] No more data');
});
▶ 试一试
TEXT 📖 仅展示
[Data] Received: "Hello"
[Data] Received: " "
[Data] Received: "World"
[End] No more data


10. 综合示例:事件驱动任务调度器

构建一个 TaskScheduler 类,支持注册任务、触发事件、多个监听器响应。模拟真实场景中任务创建后的通知链。

JAVASCRIPT
const EventEmitter = require('events');

class TaskScheduler extends EventEmitter {
  constructor() {
    super();
    this.tasks = new Map();
    this.nextId = 1;
  }

  addTask(name, payload) {
    const id = this.nextId++;
    const task = {
      id,
      name,
      payload,
      createdAt: new Date().toISOString(),
      status: 'pending'
    };
    this.tasks.set(id, task);
    this.emit('task:added', task);
    return task;
  }

  startTask(id) {
    const task = this.tasks.get(id);
    if (!task) {
      this.emit('error', new Error(`Task #${id} not found`));
      return;
    }
    task.status = 'running';
    task.startedAt = new Date().toISOString();
    this.emit('task:started', task);
  }

  completeTask(id, result) {
    const task = this.tasks.get(id);
    if (!task) {
      this.emit('error', new Error(`Task #${id} not found`));
      return;
    }
    task.status = 'completed';
    task.result = result;
    task.completedAt = new Date().toISOString();
    this.emit('task:completed', task);
  }

  failTask(id, reason) {
    const task = this.tasks.get(id);
    if (!task) {
      this.emit('error', new Error(`Task #${id} not found`));
      return;
    }
    task.status = 'failed';
    task.reason = reason;
    task.failedAt = new Date().toISOString();
    this.emit('task:failed', task);
  }

  getStats() {
    const stats = { total: this.tasks.size, pending: 0, running: 0, completed: 0, failed: 0 };
    for (const task of this.tasks.values()) {
      stats[task.status]++;
    }
    return stats;
  }
}

const scheduler = new TaskScheduler();

scheduler.on('error', (err) => {
  console.error(`[ERROR] ${err.message}`);
});

scheduler.on('task:added', (task) => {
  console.log(`[Logger] Task #${task.id} "${task.name}" added at ${task.createdAt}`);
});

scheduler.on('task:added', (task) => {
  console.log(`[Notifier] New task available: ${task.name}`);
});

scheduler.on('task:started', (task) => {
  console.log(`[Executor] Task #${task.id} is now running...`);
});

scheduler.on('task:completed', (task) => {
  console.log(`[Reporter] Task #${task.id} completed with result: ${task.result}`);
  console.log(`[Cleaner] Releasing resources for task #${task.id}`);
});

scheduler.on('task:failed', (task) => {
  console.log(`[Alerter] Task #${task.id} failed: ${task.reason}`);
});

console.log('=== Adding Tasks ===');
const t1 = scheduler.addTask('Data Import', { source: 'api.example.com', rows: 5000 });
const t2 = scheduler.addTask('Report Generation', { format: 'PDF', quarter: 'Q4' });

console.log('\n=== Starting Tasks ===');
scheduler.startTask(t1.id);
scheduler.startTask(t2.id);

console.log('\n=== Completing / Failing Tasks ===');
scheduler.completeTask(t1.id, '5000 rows imported successfully');
scheduler.failTask(t2.id, 'PDF renderer service unavailable');

console.log('\n=== Stats ===');
console.log(scheduler.getStats());

console.log('\n=== Invalid Operation ===');
scheduler.startTask(999);
TEXT 📖 仅展示
=== Adding Tasks ===
[Logger] Task #1 "Data Import" added at 2025-07-03T10:00:00.000Z
[Notifier] New task available: Data Import
[Logger] Task #2 "Report Generation" added at 2025-07-03T10:00:01.000Z
[Notifier] New task available: Report Generation

=== Starting Tasks ===
[Executor] Task #1 is now running...
[Executor] Task #2 is now running...

=== Completing / Failing Tasks ===
[Reporter] Task #1 completed with result: 5000 rows imported successfully
[Cleaner] Releasing resources for task #1
[Alerter] Task #2 failed: PDF renderer service unavailable

=== Stats ===
{ total: 2, pending: 0, running: 0, completed: 1, failed: 1 }

=== Invalid Operation ===
[ERROR] Task #999 not found

❓ 常见问题

Q EventEmitter 和浏览器中的事件有什么区别?
A Node.js 的 EventEmitter 是同步执行的,监听器按注册顺序依次运行;浏览器 DOM 事件是异步的,经过捕获/冒泡阶段,且事件对象结构与 Node.js 不同。
Q 忘记监听 error 事件会怎样?
A 如果 emit('error') 时没有注册任何 error 监听器,Node.js 会将错误作为未捕获异常抛出,进程直接退出(除非设置了 process.on('uncaughtException'))。
Q 一个事件最多能有多少个监听器?
A 默认上限为 10 个,超过会打印内存泄漏警告(不阻止执行)。可通过 emitter.setMaxListeners(n) 调整,设为 0 则无限制。
Q emit() 的返回值是什么?
A 如果该事件至少有一个监听器,返回 true;如果没有任何监听器,返回 false。可用于判断事件是否被处理。
Q 如何实现只监听一次就自动移除?
A 使用 once() 方法注册监听器,触发一次后自动移除,适用于初始化、一次性信号等场景。
Q off() 和 removeListener() 有什么区别?
A 功能完全相同,off() 是 Node.js v10 新增的 removeListener() 别名,语义更简洁,新代码推荐使用 off()
Q 监听器中的 this 指向什么?
A 使用 ES6 箭头函数时 this 继承外层作用域;使用普通函数时 this 指向 EventEmitter 实例,除非通过 bind() 绑定了其他对象。

📖 小节


📝 作业

  1. 完成本课所有代码示例,确保每个示例都能正确运行
  2. 修改综合示例,添加自己的扩展功能
  3. 查阅官方文档,找出本课未涉及的1-2个API并编写测试代码
  4. 思考:在实际项目中,你会如何应用本课学到的知识?
  5. 尝试将本课知识与前面课程的内容结合,构建一个小项目
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏