Node.js 的核心设计哲学是事件驱动和非阻塞 I/O。许多核心模块(如 http、fs、stream)都基于事件机制。而这一切的基石就是 events 模块和 EventEmitter 类。本章将深入讲解如何在 Node.js 中使用事件,以及如何创建自己的事件发射器。
事件驱动编程是一种编程范式,程序的执行流程由事件(如用户操作、传感器输出、消息等)决定。在 Node.js 中,许多对象都会触发事件,例如 TCP 服务器在有新连接时触发 connection 事件,可读流在有数据时触发 data 事件。这些对象都是 EventEmitter 的实例,通过绑定事件监听器来响应事件。
events 模块只提供了一个 EventEmitter 类,它是 Node.js 事件机制的核心。首先需要引入模块:
const EventEmitter = require('events');
然后创建 EventEmitter 的实例:
const myEmitter = new EventEmitter();
on 方法用于注册事件监听器,emit 方法用于触发事件。当事件被触发时,所有注册的监听器会按注册顺序同步执行。
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
// 注册事件监听器
myEmitter.on('greet', (name) => {
console.log(`Hello, ${name}!`);
});
// 触发事件
myEmitter.emit('greet', 'Node.js'); // 输出: Hello, Node.js!
emit 可以传递任意数量的参数给监听器函数。
使用 once 方法注册的监听器最多只会被触发一次,触发后立即移除。
myEmitter.once('onceEvent', () => {
console.log('这个只会执行一次');
});
myEmitter.emit('onceEvent'); // 输出
myEmitter.emit('onceEvent'); // 无输出
removeListener 方法用于移除指定的监听器。Node.js 10+ 也提供了别名 off。
const callback = () => console.log('执行');
myEmitter.on('test', callback);
// 移除监听器
myEmitter.removeListener('test', callback);
// 或 myEmitter.off('test', callback);
myEmitter.emit('test'); // 无输出
myEmitter.removeAllListeners('test'); // 移除指定事件的所有监听器
myEmitter.removeAllListeners(); // 移除所有事件的所有监听器
listenerCount 返回指定事件的监听器数量。listeners 返回指定事件的监听器函数数组。
console.log(myEmitter.listenerCount('greet')); // 整数
console.log(myEmitter.listeners('greet')); // [Function]
在监听器中,this 指向 EventEmitter 实例本身。如果使用箭头函数,则 this 由词法作用域决定,不会指向实例。
myEmitter.on('test', function() {
console.log(this === myEmitter); // true
});
myEmitter.on('test', () => {
console.log(this === myEmitter); // false,this 是外部上下文
});
当 EventEmitter 实例中发生错误时,通常应该触发 error 事件。如果没有为 error 事件注册监听器,Node.js 会将错误抛出,导致进程崩溃。
myEmitter.on('error', (err) => {
console.error('发生错误:', err.message);
});
myEmitter.emit('error', new Error('出错了')); // 被捕获,不会崩溃
EventEmitter 默认会同步调用所有监听器。如果希望异步执行,可以使用 setImmediate 或 process.nextTick。
myEmitter.on('async', () => {
setImmediate(() => {
console.log('异步执行');
});
});
myEmitter.emit('async');
console.log('同步执行后');
// 输出顺序:同步执行后 -> 异步执行
在实际开发中,我们常常创建自定义类,让它继承 EventEmitter,从而拥有事件能力。这可以通过 ES6 的 extends 实现。
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {
constructor(name) {
super();
this.name = name;
}
start() {
console.log(`${this.name} 启动`);
this.emit('start', this.name);
}
process(data) {
this.emit('data', data);
}
}
const myObj = new MyEmitter('测试对象');
myObj.on('start', (name) => {
console.log(`事件:${name} 已启动`);
});
myObj.on('data', (data) => {
console.log('收到数据:', data);
});
myObj.start();
myObj.process('hello');
每个 EventEmitter 实例默认最多为同一事件注册 10 个监听器,超过会发出警告。可以通过 setMaxListeners 修改这个限制。
myEmitter.setMaxListeners(20);
console.log(myEmitter.getMaxListeners()); // 20
EventEmitter 实例本身会触发两个内置事件:
newListener:当添加新监听器时触发。removeListener:当移除监听器时触发。myEmitter.on('newListener', (event, listener) => {
console.log(`添加了 ${event} 事件的监听器`);
});
myEmitter.on('removeListener', (event, listener) => {
console.log(`移除了 ${event} 事件的监听器`);
});
const fn = () => {};
myEmitter.on('foo', fn);
myEmitter.off('foo', fn);
使用 EventEmitter 实现一个发布-订阅模式的任务队列:
const EventEmitter = require('events');
class TaskQueue extends EventEmitter {
constructor(concurrency) {
super();
this.concurrency = concurrency;
this.queue = [];
this.running = 0;
}
pushTask(task) {
this.queue.push(task);
process.nextTick(() => this.next());
}
next() {
if (this.running >= this.concurrency || this.queue.length === 0) {
return;
}
const task = this.queue.shift();
this.running++;
task().then((result) => {
this.running--;
this.emit('task-complete', result);
this.next();
}).catch(err => {
this.running--;
this.emit('error', err);
this.next();
});
}
}
const queue = new TaskQueue(2);
queue.on('task-complete', (result) => {
console.log('任务完成:', result);
});
queue.on('error', console.error);
// 模拟异步任务
queue.pushTask(() => new Promise(resolve => {
setTimeout(() => resolve('任务1'), 1000);
}));
queue.pushTask(() => new Promise(resolve => {
setTimeout(() => resolve('任务2'), 500);
}));
queue.pushTask(() => new Promise(resolve => {
setTimeout(() => resolve('任务3'), 800);
}));
error 事件注册监听器,避免进程崩溃。once 代替 on 如果只需要响应一次事件。事件模块是 Node.js 异步编程的基石。通过 EventEmitter,我们可以轻松实现发布-订阅模式,构建松耦合、可扩展的系统。本章介绍了 EventEmitter 的核心 API,并通过实例展示了其应用。在后续章节中,我们将学习流(Stream)模块,它也是基于事件实现的。