events 事件系统

NodeJS的事件系统是其异步编程模型的核心基础之一。events模块提供了EventEmitter类,它是NodeJS中实现事件驱动架构的关键组件,许多NodeJS核心模块(如fshttpstream等)都继承自EventEmitter,这使得事件机制贯穿于整个NodeJS生态系统。

EventEmitter基础

EventEmitter是NodeJS事件系统的核心类,它提供了事件的注册、触发和移除等功能,下面是一个最基本的使用示例。

import { EventEmitter } from "events";

// 创建EventEmitter实例
const emitter = new EventEmitter();

// 注册事件监听器
emitter.on("greet", (name) => {
  console.log(`Hello, ${name}!`);
});

// 触发事件
emitter.emit("greet", "NodeJS");

运行结果。

Hello, NodeJS!

代码中,我们首先通过import引入了EventEmitter类,然后创建了一个实例。使用on()方法注册了一个名为greet的事件监听器,最后通过emit()方法触发该事件并传递参数。

注册事件监听器

EventEmitter提供了多种注册事件监听器的方式,以满足不同的使用场景。

on()方法

on()是最常用的事件注册方法,它会将监听器添加到监听器数组的末尾,且可以多次触发。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

emitter.on("data", (msg) => {
  console.log(`Received: ${msg}`);
});

emitter.emit("data", "First message");
emitter.emit("data", "Second message");

运行结果如下。

Received: First message
Received: Second message

once()方法

once()方法注册的监听器只会触发一次,触发后自动移除。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

emitter.once("connect", () => {
  console.log("Connected!");
});

emitter.emit("connect"); // 输出: Connected!
emitter.emit("connect"); // 不会触发,监听器已被移除

prependListener()方法

prependListener()方法将监听器添加到监听器数组的开头,使其优先执行。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

emitter.on("message", () => {
  console.log("Listener 1");
});

emitter.prependListener("message", () => {
  console.log("Listener 2 (prepended)");
});

emitter.emit("message");

运行结果如下。

Listener 2 (prepended)
Listener 1

移除事件监听器

在某些场景下,我们需要移除已注册的事件监听器,以避免内存泄漏或重复执行。

off()方法

off()方法用于移除指定的事件监听器,注意必须传入与注册时相同的函数引用。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

const handler = (msg) => {
  console.log(`Message: ${msg}`);
};

emitter.on("message", handler);
emitter.emit("message", "Hello"); // 输出: Message: Hello

emitter.off("message", handler);
emitter.emit("message", "World"); // 无输出,监听器已被移除

removeAllListeners()方法

removeAllListeners()方法用于移除指定事件的所有监听器,或移除所有事件的所有监听器。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

emitter.on("event1", () => console.log("Event 1 - Handler 1"));
emitter.on("event1", () => console.log("Event 1 - Handler 2"));
emitter.on("event2", () => console.log("Event 2 - Handler 1"));

// 移除event1的所有监听器
emitter.removeAllListeners("event1");

emitter.emit("event1"); // 无输出
emitter.emit("event2"); // 输出: Event 2 - Handler 1

// 移除所有事件的所有监听器
emitter.removeAllListeners();

向监听器传递参数

emit()方法支持向监听器传递参数。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

emitter.on("userInfo", (name, age, email) => {
  console.log(`Name: ${name}`);
  console.log(`Age: ${age}`);
  console.log(`Email: ${email}`);
});

emitter.emit("userInfo", "Tom", 18, "tom@example.com");

运行结果如下。

Name: Tom
Age: 18
Email: tom@example.com

监听器数量限制

默认情况下,每个事件最多可以注册10个监听器。超过这个限制时,NodeJS会输出警告信息(但不会阻止添加)。我们可以通过setMaxListeners()方法修改这个限制。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

// 查看当前最大监听器数量
console.log(emitter.getMaxListeners()); // 输出: 10

// 修改最大监听器数量
emitter.setMaxListeners(20);
console.log(emitter.getMaxListeners()); // 输出: 20

// 设置为0或Infinity表示不限制数量
emitter.setMaxListeners(0);

获取事件和监听器信息

EventEmitter提供了一些方法用于获取当前实例的事件和监听器信息。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

const handler1 = () => console.log("Handler 1");
const handler2 = () => console.log("Handler 2");

emitter.on("event1", handler1);
emitter.on("event1", handler2);
emitter.on("event2", handler1);

// 获取所有已注册的事件名称
console.log(emitter.eventNames()); // 输出: [ 'event1', 'event2' ]

// 获取指定事件的监听器数量
console.log(emitter.listenerCount("event1")); // 输出: 2

// 获取指定事件的监听器数组
console.log(emitter.listeners("event1")); // 输出: [ [Function: handler1], [Function: handler2] ]

内置事件

EventEmitter本身会触发一些内置事件,我们可以监听这些事件来追踪监听器的变化。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

// 当添加新的监听器时触发
emitter.on("newListener", (eventName, listener) => {
  console.log(`New listener added for: ${eventName}`);
});

// 当移除监听器时触发
emitter.on("removeListener", (eventName, listener) => {
  console.log(`Listener removed for: ${eventName}`);
});

const handler = () => console.log("Hello");
emitter.on("greet", handler); // 触发newListener
emitter.off("greet", handler); // 触发removeListener

运行结果如下。

New listener added for: greet
Listener removed for: greet

错误事件处理

在NodeJS中,error事件是一个特殊的事件,如果EventEmitter触发了error事件但没有注册对应的监听器,NodeJS会抛出异常并终止进程,我们可以为error事件注册监听器,拦截这一过程。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

// 注册error事件监听器
emitter.on("error", (err) => {
  console.error(`Error occurred: ${err.message}`);
});

// 触发error事件
emitter.emit("error", new Error("Something went wrong"));

运行结果如下。

Error occurred: Something went wrong

如果不注册error事件监听器会导致程序崩溃。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

// 未注册error监听器,触发error事件会导致程序崩溃
emitter.emit("error", new Error("Unhandled error"));
// 抛出异常: Error: Unhandled error

继承EventEmitter

在实际开发中,我们可以创建自定义类并继承EventEmitter,这样我们的类就具备了事件能力。

import { EventEmitter } from "events";

class DataStream extends EventEmitter {
  constructor() {
    super();
    this.data = [];
  }

  addData(item) {
    this.data.push(item);
    this.emit("dataAdded", item);

    if (this.data.length >= 5) {
      this.emit("bufferFull", this.data);
    }
  }

  clear() {
    this.data = [];
    this.emit("cleared");
  }
}

const stream = new DataStream();

stream.on("dataAdded", (item) => {
  console.log(`Data added: ${item}`);
});

stream.on("bufferFull", (data) => {
  console.log(`Buffer is full: [${data.join(", ")}]`);
});

stream.on("cleared", () => {
  console.log("Data cleared");
});

stream.addData("A");
stream.addData("B");
stream.addData("C");
stream.addData("D");
stream.addData("E");
stream.clear();

运行结果如下。

Data added: A
Data added: B
Data added: C
Data added: D
Data added: E
Buffer is full: [A, B, C, D, E]
Data cleared

异步事件处理

EventEmitter的事件触发是同步的,所有监听器会按照注册顺序依次执行,不过监听器中可以使用异步函数作为处理逻辑。

import { EventEmitter } from "events";

const emitter = new EventEmitter();

emitter.on("asyncTask", async (taskId) => {
  console.log(`Task ${taskId} started`);
  await new Promise((resolve) => setTimeout(resolve, 1000));
  console.log(`Task ${taskId} completed`);
});

console.log("Before emit");
emitter.emit("asyncTask", 1);
console.log("After emit");

运行结果如下。

Before emit
Task 1 started
After emit
Task 1 completed

注意观察输出顺序,emit()本身是同步返回的,但监听器内部的异步操作会在后续执行。

使用Promise包装事件

events提供了once()方法可以将事件转换为Promise,方便在异步代码中使用。

import { EventEmitter, once } from "events";

const emitter = new EventEmitter();

const waitForEvent = async () => {
  console.log("Waiting for event...");

  // 1秒后触发事件
  setTimeout(() => {
    emitter.emit("ready", "Data is ready");
  }, 1000);

  // 等待事件触发
  const [result] = await once(emitter, "ready");
  console.log(`Received: ${result}`);
};

waitForEvent();

运行结果如下。

Waiting for event...
Received: Data is ready
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。