NestJS依赖注入存储在Map中的类方法为何未定义?
问题描述
我创建了一个EventService,用来存储通过自定义装饰器添加的自定义事件。目前流程看似正常,但存在一个问题:当尝试获取存储的事件类时,其内部方法全是undefined。奇怪的是,类本身看起来已经完成初始化了。请问可能的原因是什么?
event.service.ts
@Injectable() /** * @class EventService */ export class EventService { /** * @private * @readonly * @type {Logger} */ private readonly logger: Logger = new Logger(EventService.name); /** * Event Storage * * @private * @readonly * @type {Map<string, BaseEvent>} */ private readonly _events: Map<string, BaseEvent> = new Map< string, BaseEvent >(); /** * Add Event * * @public * @async * @param {string} key Event Name * @param {BaseEvent} event Event Class * @param {boolean} overwrite Overwrite Event in Map * @returns {Promise<void>} */ public async add( key: string, event: BaseEvent, overwrite = false ): Promise<void> { // eslint-disable-next-line @typescript-eslint/no-explicit-any const className = (event as any).name; const hasRegistered = await this.has(key); if (hasRegistered && !overwrite) { this.logger.error(`${key} already registered with: ${className}`); return; } if (hasRegistered && overwrite) { this._events.set(key, event); this.logger.log( `${key} got overwritten by event class: ${className}` ); return; } this._events.set(key, event); console.log(event); console.log(event.test); this.logger.log(`${key} was registered to: ${className}`); } }
event.decorator.ts
/** * Bind Event to Class * * @function * @param {string} name Event Name * @param {boolean} client Determinate if its a client event * @param {string} overwrite Overwrite existing event */ export function Event(name: string, client = false, overwrite = false): any { return (constructor: any) => { return class extends constructor { constructor(...args: any[]) { super(args); const eventService: EventService = this.eventService[0]; eventService.add(name, constructor, overwrite); } }; }; }
player-join.event.ts
@Injectable() @Event('playerJoin') export class PlayerJoinEvent extends BaseEvent { /** * @private * @readonly * @type {Logger} */ public readonly logger: Logger = new Logger(PlayerJoinEvent.name); /** * Handle Player Join * * @public * @async * @param {any} player Joining Player * @returns {Promise<void>} */ public async execute(player: string): Promise<void> { this.logger.log(`${player} joined the server`); } public test(): void { this.logger.debug('hi'); } }
控制台输出
[Nest] 22508 - 20.09.2022, 19:44:48 LOG [NestFactory] Starting Nest application... [Nest] 22508 - 20.09.2022, 19:44:48 LOG [InstanceLoader] AppModule dependencies initialized +24ms [class PlayerJoinEvent extends BaseEvent] // these both logs are in the EventService.ts after adding them undefined [Nest] 22508 - 20.09.2022, 19:44:48 LOG [EventService] playerJoin was registered to: PlayerJoinEvent
问题原因与解决办法
核心问题
你在装饰器里传给EventService.add的是类的构造函数(constructor),而不是类的实例对象。构造函数本身不会包含实例方法——实例方法是类被实例化后,挂载在实例对象上的,所以你访问event.test自然会得到undefined。
看装饰器里的关键代码:
eventService.add(name, constructor, overwrite);
这里的constructor就是PlayerJoinEvent这个类本身,不是它的实例。
解决步骤
- 修改装饰器,传入类实例
把装饰器里的constructor换成this(当前类的实例),同时修正super的参数传递方式(展开args而不是直接传数组):
export function Event(name: string, client = false, overwrite = false): any { return (constructor: any) => { return class extends constructor { constructor(...args: any[]) { super(...args); // 展开参数,避免父类构造函数收到数组 const eventService: EventService = this.eventService[0]; // 传入当前实例,而非构造函数 eventService.add(name, this, overwrite); } }; }; }
- 调整
EventService.add的类名获取方式
原来通过(event as any).name获取的是构造函数的名称,现在传入的是实例,需要改成从实例的constructor属性获取类名:
// 替换原来的className获取逻辑 const className = event.constructor.name;
这样修改后,EventService存储的就是类的实例,实例上的test、execute等方法就能正常访问了。
内容的提问来源于stack exchange,提问作者NECMI
相关产品推荐
相关产品推荐

