简体   繁体   中英

NestJS throw from ExceptionFilter

I try to use an ExceptionFilter to map exceptions to their HTTP counterpart.

This is my code :

@Catch(EntityNotFoundError)
export class EntityNotFoundFilter implements ExceptionFilter {
    catch(exception: EntityNotFoundError, _host: ArgumentsHost) {
        throw new NotFoundException(exception.message);
    }
}

But, when the filter code is executed, I got a UnhandledPromiseRejectionWarning

 (node:3065) UnhandledPromiseRejectionWarning: Error: [object Object]
    at EntityNotFoundFilter.catch ([...]/errors.ts:32:15)
    at ExceptionsHandler.invokeCustomFilters ([...]/node_modules/@nestjs/core/exceptions/exceptions-handler.js:49:26)
     at ExceptionsHandler.next ([...]/node_modules/@nestjs/core/exceptions/exceptions-handler.js:13:18)
     at [...]/node_modules/@nestjs/core/router/router-proxy.js:12:35
     at <anonymous>
     at process._tickCallback (internal/process/next_tick.js:182:7)
 (node:3065) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 5)

How can I fix this ?

The ExceptionFilter is always the last place that gets called before a response is sent out, it is responsible for building the response. You cannot rethrow an exception from within an ExceptionFilter .

@Catch(EntityNotFoundError)
export class EntityNotFoundFilter implements ExceptionFilter {
  catch(exception: EntityNotFoundError, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse();
      response.status(404).json({ message: exception.message });
  }
}

Alternatively, you can create an Interceptor that transforms your errors:

@Injectable()
export class NotFoundInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // next.handle() is an Observable of the controller's result value
    return next.handle()
      .pipe(catchError(error => {
        if (error instanceof EntityNotFoundError) {
          throw new NotFoundException(error.message);
        } else {
          throw error;
        }
      }));
  }
}

Try it out in this codesandbox .

The key here is to extend the BaseExceptionFilter and delegate to the super class rather than throwing:

import { BaseExceptionFilter } from '@nestjs/core';
// .. your other imports

@Catch(EntityNotFoundError)
export class EntityNotFoundFilter extends BaseExceptionFilter {
    catch(exception: EntityNotFoundError, host: ArgumentsHost) {
        super.catch(new NotFoundException(exception.message, host));
    }
}

Be sure to pass in the applicationRef argument when constructing your filter during your application bootstrapping, because the BaseExceptionFilter needs this property to behave correctly

import { HttpAdapterHost } from '@nestjs/core';
// .. your other imports

async function bootstrap(): Promise<void> {
  // .. blah blah
  const { httpAdapter } = app.get(HttpAdapterHost);
  app.useGlobalFilters(new GeneralErrorFilter(httpAdapter));
  // .. blah blah
}

This will result in the default error handling you would receive if you had thrown the same error.

Base on Kim Kern solution I've created this abstract class

export abstract class AbstractErrorInterceptor<T> implements NestInterceptor {
    protected interceptedType: new (...args) => T;

    intercept(
        context: ExecutionContext,
        call$: Observable<any>,
    ): Observable<any> | Promise<Observable<any>> {
        return call$.pipe(
            catchError(exception => {
                if (exception instanceof this.interceptedType) {
                    this.handleError(exception);
                }
                throw exception;
            }),
        );
    }

    abstract handleError(exception: T);
}

And some implementations

export class EntityNotFoundFilter extends AbstractErrorInterceptor<EntityNotFoundError> {
    interceptedType = EntityNotFoundError;

    handleError(exception: EntityNotFoundError) {
        throw new NotFoundException(exception.message);
    }
}

It seems strange that you are creating your own version of the HTTP based exception classes that already ship with NestJS. By default, these will be automatically converted to HTTP responses with the correct error codes. You're adding overhead with Interceptors and abstract class implementations when instead you can just throw NestJS errors and get it for free. This is the built-in mechanism that you were referring to.

throw new BadRequestException('you done goofed');

results in:

{"statusCode":400,"error":"Bad Request","message":"you done goofed"}

Codesandbox (adapted from Kim's)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM