Package Event Emitter (@nestjs/event-emitter) cung cấp một cài đặt observer đơn giản, cho phép bạn subscribe và lắng nghe nhiều event khác nhau xảy ra trong ứng dụng của bạn. Event là một cách tuyệt vời để decoupling các phần khác nhau của ứng dụng, vì một event duy nhất có thể có nhiều listener không phụ thuộc vào nhau.
EventEmitterModule sử dụng package eventemitter2 bên dưới.
Getting started (Bắt đầu)
Đầu tiên, cài package cần thiết:
$ npm i --save @nestjs/event-emitter
Sau khi cài xong, import EventEmitterModule vào AppModule gốc và chạy static method forRoot() như dưới đây:
import { Module } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';
@Module({
imports: [
EventEmitterModule.forRoot()
],
})
export class AppModule {}
Lệnh gọi .forRoot() khởi tạo event emitter và đăng ký mọi event listener khai báo tồn tại trong app của bạn. Việc đăng ký diễn ra khi lifecycle hook onApplicationBootstrap xảy ra, đảm bảo tất cả module đã được load và đã khai báo mọi scheduled job.
Để cấu hình instance EventEmitter bên dưới, truyền configuration object vào phương thức .forRoot(), như sau:
EventEmitterModule.forRoot({
// đặt thành `true` để dùng wildcard
wildcard: false,
// dấu phân tách dùng để chia namespace
delimiter: '.',
// đặt thành `true` nếu bạn muốn emit event newListener
newListener: false,
// đặt thành `true` nếu bạn muốn emit event removeListener
removeListener: false,
// số lượng listener tối đa có thể gán cho một event
maxListeners: 10,
// hiển thị tên event trong memory leak message khi số listener vượt quá số lượng tối đa
verboseMemoryLeak: false,
// tắt việc throw uncaughtException nếu một error event được emit và không có listener nào
ignoreErrors: false,
});
Dispatching events (Bắn event)
Để dispatch (nghĩa là bắn) một event, đầu tiên inject EventEmitter2 bằng constructor injection tiêu chuẩn:
constructor(private eventEmitter: EventEmitter2) {}
Gợi ý Import EventEmitter2 từ package @nestjs/event-emitter.
Sau đó dùng nó trong một class như sau:
this.eventEmitter.emit(
'order.created',
new OrderCreatedEvent({
orderId: 1,
payload: {},
}),
);
Listening to events (Lắng nghe event)
Để khai báo một event listener, decorate một method với decorator @OnEvent() đặt trước định nghĩa method chứa code cần được thực thi, như sau:
@OnEvent('order.created')
handleOrderCreatedEvent(payload: OrderCreatedEvent) {
// xử lý event "OrderCreatedEvent"
}
Cảnh báo Event subscriber không thể là request-scoped.
Tham số đầu tiên có thể là string hoặc symbol cho một event emitter đơn giản, và string | symbol | Array<string | symbol> trong trường hợp wildcard emitter.
Tham số thứ hai (optional) là một listener options object như sau:
export type OnEventOptions = OnOptions & {
/**
* Nếu là "true", thêm listener được cho vào đầu (thay vì cuối) mảng listener.
*
* @see https://github.com/EventEmitter2/EventEmitter2#emitterprependlistenerevent-listener-options
*
* @default false
*/
prependListener?: boolean;
/**
* Nếu là "true", callback onEvent sẽ không throw error khi xử lý event. Ngược lại, nếu là "false" nó sẽ throw error.
*
* @default true
*/
suppressErrors?: boolean;
};
Gợi ý Đọc thêm về options object OnOptions từ eventemitter2.
@OnEvent('order.created', { async: true })
handleOrderCreatedEvent(payload: OrderCreatedEvent) {
// xử lý event "OrderCreatedEvent"
}
Để dùng namespace/wildcard, truyền option wildcard vào phương thức EventEmitterModule#forRoot(). Khi namespace/wildcard được bật, event có thể là string (foo.bar) được phân tách bằng một delimiter, hoặc là mảng (['foo', 'bar']). Delimiter cũng có thể cấu hình qua property delimiter. Với tính năng namespace được bật, bạn có thể subscribe event bằng wildcard:
@OnEvent('order.*')
handleOrderEvents(payload: OrderCreatedEvent | OrderRemovedEvent | OrderUpdatedEvent) {
// xử lý event
}
Lưu ý rằng wildcard này chỉ áp dụng cho một block. Tham số order.* sẽ khớp, ví dụ, các event order.created và order.shipped nhưng không khớp order.delayed.out_of_stock. Để lắng nghe những event như vậy,
dùng pattern multilevel wildcard (nghĩa là, **), được mô tả trong tài liệu của EventEmitter2.
Với pattern này, bạn có thể, ví dụ, tạo một event listener bắt tất cả event.
@OnEvent('**')
handleEverything(payload: any) {
// xử lý event
}
Gợi ý Class EventEmitter2 cung cấp một số method hữu ích để tương tác với event, như waitFor và onAny. Bạn có thể đọc thêm về chúng tại đây.
Preventing event loss (Ngăn mất event)
Các event được bắn trước hoặc trong lúc lifecycle hook onApplicationBootstrap—ví dụ từ module constructor hoặc method onModuleInit—có thể bị mất vì EventSubscribersLoader có thể chưa hoàn tất việc thiết lập listener.
Để tránh vấn đề này, bạn có thể dùng method waitUntilReady của EventEmitterReadinessWatcher, trả về một promise sẽ resolve khi tất cả listener đã được đăng ký xong. Method này có thể được gọi trong lifecycle hook onApplicationBootstrap của một module để đảm bảo mọi event đều được bắt đúng cách.
await this.eventEmitterReadinessWatcher.waitUntilReady();
this.eventEmitter.emit(
'order.created',
new OrderCreatedEvent({ orderId: 1, payload: {} }),
);
Lưu ý Điều này chỉ cần thiết cho các event được bắn trước khi lifecycle hook onApplicationBootstrap hoàn tất.
Example (Ví dụ)
Một ví dụ hoạt động có sẵn tại đây.
Ghi chú người dịch Cơ chế này về bản chất là pub/sub trong tiến trình (in-process), khác với event queue của message broker (RabbitMQ, Kafka) — event chỉ tồn tại trong bộ nhớ của instance đang chạy, mất đi khi restart, và không chia sẻ giữa nhiều instance. Nếu bạn cần event bền (persist), replay được, hoặc chạy across nhiều service, đây không phải công cụ phù hợp — hãy dùng message broker thật.
Ghi chú người dịch Cảnh báo “Event subscriber không thể là request-scoped” dễ bị bỏ qua khi vibe-code: nếu bạn đặt provider chứa @OnEvent() ở request scope (ví dụ vì nó cần inject REQUEST), EventSubscribersLoader sẽ không đăng ký được listener đúng cách, vì listener cần tồn tại lâu dài (singleton) để lắng nghe xuyên suốt vòng đời app, không gắn với một request cụ thể.