Techniques

事件

学习如何在 NestJS 应用程序中使用事件发射器模式来解耦应用程序的各个方面,通过订阅和监听各种事件来实现观察者模式。

事件

Event Emitter 包 (@nestjs/event-emitter) 提供了一个简单的观察者实现,允许你订阅和监听应用程序中发生的各种事件。事件是解耦应用程序各个方面的绝佳方式,因为单个事件可以有多个彼此不依赖的监听器。

EventEmitterModule 内部使用 eventemitter2 包。

开始使用

首先安装所需的包:

$ npm i --save @nestjs/event-emitter

安装完成后,将 EventEmitterModule 导入到根 AppModule 中,并运行 forRoot() 静态方法,如下所示:

@@filename(app.module)
import { Module } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';

@Module({
  imports: [
    EventEmitterModule.forRoot()
  ],
})
export class AppModule {}

.forRoot() 调用初始化事件发射器并注册应用程序中存在的任何声明式事件监听器。注册发生在 onApplicationBootstrap 生命周期钩子期间,确保所有模块都已加载并声明了任何计划的作业。

要配置底层的 EventEmitter 实例,请将配置对象传递给 .forRoot() 方法,如下所示:

EventEmitterModule.forRoot({
  // 设置为 `true` 以使用通配符
  wildcard: false,
  // 用于分割命名空间的分隔符
  delimiter: '.',
  // 如果你想发射 newListener 事件,设置为 `true`
  newListener: false,
  // 如果你想发射 removeListener 事件,设置为 `true`
  removeListener: false,
  // 可以分配给事件的监听器的最大数量
  maxListeners: 10,
  // 当分配的监听器数量超过最大值时,在内存泄漏消息中显示事件名称
  verboseMemoryLeak: false,
  // 如果发射了错误事件且没有监听器,禁用抛出 uncaughtException
  ignoreErrors: false,
});

分发事件

要分发(即触发)事件,首先使用标准构造函数注入来注入 EventEmitter2

constructor(private eventEmitter: EventEmitter2) {}

提示@nestjs/event-emitter 包中导入 EventEmitter2

然后在类中使用它,如下所示:

this.eventEmitter.emit(
  'order.created',
  new OrderCreatedEvent({
    orderId: 1,
    payload: {},
  }),
);

监听事件

要声明事件监听器,请在包含要执行代码的方法定义前使用 @OnEvent() 装饰器装饰方法,如下所示:

@OnEvent('order.created')
handleOrderCreatedEvent(payload: OrderCreatedEvent) {
  // 处理和处理 "OrderCreatedEvent" 事件
}

警告 事件订阅者不能是请求作用域的。

第一个参数可以是简单事件发射器的 stringsymbol,在通配符发射器的情况下可以是 string | symbol | Array<string | symbol>

第二个参数(可选)是监听器选项对象,如下所示:

export type OnEventOptions = OnOptions & {
  /**
   * 如果为 "true",则将给定的监听器前置(而不是追加)到监听器数组中。
   *
   * @see https://github.com/EventEmitter2/EventEmitter2#emitterprependlistenerevent-listener-options
   *
   * @default false
   */
  prependListener?: boolean;

  /**
   * 如果为 "true",onEvent 回调在处理事件时不会抛出错误。否则,如果为 "false",它将抛出错误。
   *
   * @default true
   */
  suppressErrors?: boolean;
};

提示eventemitter2 了解更多关于 OnOptions 选项对象的信息。

@OnEvent('order.created', { async: true })
handleOrderCreatedEvent(payload: OrderCreatedEvent) {
  // 处理和处理 "OrderCreatedEvent" 事件
}

要使用命名空间/通配符,请将 wildcard 选项传递给 EventEmitterModule#forRoot() 方法。当启用命名空间/通配符时,事件可以是由分隔符分隔的字符串(foo.bar)或数组(['foo', 'bar'])。分隔符也可以作为配置属性(delimiter)进行配置。启用命名空间功能后,你可以使用通配符订阅事件:

@OnEvent('order.*')
handleOrderEvents(payload: OrderCreatedEvent | OrderRemovedEvent | OrderUpdatedEvent) {
  // 处理和处理事件
}

请注意,这样的通配符只适用于一个块。参数 order.* 将匹配,例如,事件 order.createdorder.shipped,但不匹配 order.delayed.out_of_stock。为了监听这样的事件,请使用 多级通配符 模式(即 **),在 EventEmitter2 文档 中有描述。

使用这种模式,你可以,例如,创建一个捕获所有事件的事件监听器。

@OnEvent('**')
handleEverything(payload: any) {
  // 处理和处理事件
}

提示 EventEmitter2 类提供了几个与事件交互的有用方法,如 waitForonAny。你可以在这里了解更多信息。

防止事件丢失

onApplicationBootstrap 生命周期钩子之前或期间触发的事件——例如来自模块构造函数或 onModuleInit 方法的事件——可能会被错过,因为 EventSubscribersLoader 可能尚未完成设置监听器。

为了避免这个问题,你可以使用 EventEmitterReadinessWatcherwaitUntilReady 方法,该方法返回一个在所有监听器注册完成后解析的 promise。这个方法可以在模块的 onApplicationBootstrap 生命周期钩子中调用,以确保所有事件都被正确捕获。

await this.eventEmitterReadinessWatcher.waitUntilReady();
this.eventEmitter.emit(
  'order.created',
  new OrderCreatedEvent({ orderId: 1, payload: {} }),
);

注意 这只对在 onApplicationBootstrap 生命周期钩子完成之前发射的事件是必要的。

示例

可以在这里找到一个工作示例。