diff --git a/lib/decorators/on-event.decorator.ts b/lib/decorators/on-event.decorator.ts index 469a660c..b080b890 100644 --- a/lib/decorators/on-event.decorator.ts +++ b/lib/decorators/on-event.decorator.ts @@ -33,9 +33,9 @@ export const OnEvent = ( ): MethodDecorator => { const decoratorFactory = (target: object, key?: any, descriptor?: any) => { extendArrayMetadata( - EVENT_LISTENER_METADATA, - [{ event, options } as OnEventMetadata], - descriptor.value, + EVENT_LISTENER_METADATA, + [{ event, options } as OnEventMetadata], + descriptor.value, ); return descriptor; }; diff --git a/lib/event-subscribers.loader.ts b/lib/event-subscribers.loader.ts index fd11a155..e7d1303b 100644 --- a/lib/event-subscribers.loader.ts +++ b/lib/event-subscribers.loader.ts @@ -1,5 +1,6 @@ import { Injectable, + Logger, OnApplicationBootstrap, OnApplicationShutdown, } from '@nestjs/common'; @@ -24,6 +25,7 @@ export class EventSubscribersLoader implements OnApplicationBootstrap, OnApplicationShutdown { private readonly injector = new Injector(); + private readonly logger = new Logger('Event'); constructor( private readonly discoveryService: DiscoveryService, @@ -92,7 +94,8 @@ export class EventSubscribersLoader } else { listenerMethod( event, - (...args: unknown[]) => instance[methodKey].call(instance, ...args), + (...args: unknown[]) => + this.wrapFunctionInTryCatchBlocks(instance, methodKey, args), options, ); } @@ -135,9 +138,10 @@ export class EventSubscribersLoader moduleRef.providers, contextId, ); - return contextInstance[listenerMethodKey].call( + return this.wrapFunctionInTryCatchBlocks( contextInstance, - ...args, + listenerMethodKey, + args, ); }, options, @@ -168,4 +172,16 @@ export class EventSubscribersLoader this.moduleRef.registerRequestByContextId(payloadObjectOrArray, contextId); } + + private async wrapFunctionInTryCatchBlocks( + instance: Record, + methodKey: string, + args: unknown[], + ) { + try { + return await instance[methodKey].call(instance, ...args); + } catch (e) { + this.logger.error(e); + } + } } diff --git a/tests/e2e/module-e2e.spec.ts b/tests/e2e/module-e2e.spec.ts index 7ac0bf46..5cd6ef3b 100644 --- a/tests/e2e/module-e2e.spec.ts +++ b/tests/e2e/module-e2e.spec.ts @@ -122,6 +122,26 @@ describe('EventEmitterModule - e2e', () => { expect(customConsumer.isEmitted).toBeTruthy(); }); + it('should be able to gracefully recover when an unexpected error occurs from provider', async () => { + const eventsConsumerRef = app.get(EventsProviderConsumer); + await app.init(); + + const emitter = app.get(EventEmitter2); + const result = emitter.emit('error-handling.provider'); + + expect(eventsConsumerRef.errorHandlingCalls).toEqual(1); + expect(result).toBeTruthy(); + }); + + it('should be able to gracefully recover when an unexpected error occurs from request scoped', async () => { + await app.init(); + + const eventEmitter = app.get(EventEmitter2); + const result = eventEmitter.emit('error-handling.request-scoped'); + + expect(result).toBeTruthy(); + }); + afterEach(async () => { await app.close(); }); diff --git a/tests/src/events-provider.consumer.ts b/tests/src/events-provider.consumer.ts index d5688638..b0f51a24 100644 --- a/tests/src/events-provider.consumer.ts +++ b/tests/src/events-provider.consumer.ts @@ -5,6 +5,7 @@ import { OnEvent } from '../../lib'; export class EventsProviderConsumer { public eventPayload = {}; public stackedEventCalls = 0; + public errorHandlingCalls = 0; @OnEvent('test.*') onTestEvent(payload: Record) { @@ -16,4 +17,10 @@ export class EventsProviderConsumer { onStackedEvent() { this.stackedEventCalls++; } + + @OnEvent('error-handling.provider') + onErrorHandlingEvent() { + this.errorHandlingCalls++; + throw new Error('This is a test error'); + } } diff --git a/tests/src/events-provider.request-scoped.consumer.ts b/tests/src/events-provider.request-scoped.consumer.ts index cf5233e9..0477aa47 100644 --- a/tests/src/events-provider.request-scoped.consumer.ts +++ b/tests/src/events-provider.request-scoped.consumer.ts @@ -21,4 +21,9 @@ export class EventsProviderRequestScopedConsumer { @OnEvent('string.*') onStringPayloadEvent() {} + + @OnEvent('error-handling.request-scoped') + onErrorHandlingEvent() { + throw new Error('This is a test error'); + } }