Chapter 32

techniques-events

kisihara.c
kisihara.c
2021.05.23に更新

イベント

event-emitterパッケージ(@nestjs/event-emitter)はシンプルなオブザーバの実装を提供している。アプリケーションで発生する様々なイベントをサブスクライブしてリッスンする事ができる。イベントはアプリケーションの様々な側面を切り離すのに最適な方法と言える。一つのイベントが、お互いに依存しない複数のリスナーを持てるからだ。

EventEmitterModuleは内部でeventemitter2パッケージを使用している。

導入

まず必要なパッケージをインストールしよう。

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

インストールが終わったら、ルートのAppModuleEventEmitterModuleをインポートし、以下のようにforRoot()静的メソッドを実行する。

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

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

.fooRoot()の呼び出しはイベントエミッタを初期化し、アプリ内に存在する宣言型のイベントリスナーを登録する。登録はonApplicationBootstrapライフサイクルフックが発生した時に行われ、すべてのモジュールがロードされ、スケジュールされたジョブが宣言されている事を確認する。

基礎となるEventEmitterインスタンスを構成するために、以下のように構成オブジェクトを.fooRoot()メソッドに渡す。

EventEmitterModule.forRoot({
  // `true`にするとワイルドカードを使う
  wildcard: false,
  // 名前空間分割のために使われるデリミタ
  delimiter: '.',
  // `true`にするとnewListenerイベントを発行する
  newListener: false,
  // `true`にするとremoveListenerイベントを発行する
  removeListener: false,
  // 1つのイベントに割り当てられるリスナーの最大数
  maxListeners: 10,
  // 最大数以上のリスナーが割り当てられた時、メモリリークメッセージにイベント名を表示する
  verboseMemoryLeak: false,
  // リスナーがいないエラーイベントが発生した際uncaughtExceptionをスローする動作を無効にする
  ignoreErrors: false,
});

イベントのディスパッチ

イベントをディスパッチ(発火)するには、まず標準的なコンストラクタインジェクションを使用してEventEmitter2を注入する。

constructor(private eventEmitter: EventEmitter2) {}

HINT
EventEmitter2@nestjs/event-emitterパッケージからインポートする。

そしてクラス内で使用する。

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

イベントのリッスン

イベントリスナーを宣言するには、次のように実行されるコードを含むメソッド定義の前に@OnEvent()デコレータをつけてメソッドを装飾する。

@OnEvent('order.created')
handleOrderCreatedEvent(payload: OrderCreatedEvent) {
  // "OrderCreatedEvent"イベントを操作・処理する。
}

WARNING
イベントサブスクライバはリクエストスコープ化できない。

最初の引数は、単純なイベントエミッタの場合stringまたはsymbol、ワイルドカードエミッタの場合はstringsymbolArray<string|symbol>となる。2番目の引数(省略可能)は、リスナーのオプションオブジェクトだ(説明)。

@OnEvent('order.created', { async: true })
handleOrderCreatedEvent(payload: OrderCreatedEvent) {
  //"OrderCreatedEvent"イベントを操作・処理する。
}

名前空間/ワイルドカードを使用するには、EventEmitterModule#forRoot()メソッドにwildcardオプションを渡す。名前空間/ワイルドカードを有効にすると、イベントはデリミタで区切られた文字列(foo.bar)か配列(['foo', 'bar'])となる。区切り文字は設定プロパティ(delimiter)として設定する事も可能。名前空間の機能を有効にすると、ワイルドカードを使ってイベントをサブスクライブする事ができる。

@OnEvent('order.*')
handleOrderEvents(payload: OrderCreatedEvent | OrderRemovedEvent | OrderUpdatedEvent) {
  // イベントを操作・実行する。
}

こういったワイルドカードは1つのブロックにしか適用されないことに注意。引数order.*は、order.createdorder.shippedには適用されるが、order.delayed.out_of_stockには適用されない。そういったイベントをリッスンするためにはmultilevel wildcardパターン(例:**)を使用してほしい(説明)。

例えばこのパターンを使用すると、全てのイベントを拾うイベントリスナーを作成できる。

@OnEvent('**')
handleEverything(payload: any) {
  // イベントを操作・実行する
}

HINT
EventEmitter2クラスにはwaitForonAnyなど、イベントを処理するための便利なメソッドがいくつか用意されている。詳細

サンプル

実際に動くサンプルはこちら