对于错误的处理,是一个后台开发人员必须熟练掌握的,今天来学习下nestjs
如何处理错误。
当访问一个不存在的路由时,nestjs
会返回一个如下的错误格式:
当一个接口内部存在逻辑错误时,nestjs
会返回一个如下的错误格式:
@Get()
findAll() {
// 报错
const a: any = null;
return a.b.c;
}
这些都是 Exception Filter
做的事情。它是在 Nest 应用抛异常的时候,捕获它并返回一个对应的响应。
但是,这些响应的格式不符合我们的要求,怎么办呢?
那就需要我们自定义异常时返回的响应格式了。
自定义exception filter
首先利用脚手架创建一个exception filter
:
nest g filter father --flat --no-spec
// father.filter.ts
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
@Catch()
export class FatherFilter<T> implements ExceptionFilter {
catch(exception: T, host: ArgumentsHost) {}
}
然后在main.ts
中引入:
app.useGlobalFilters(new FatherFilter());
当然,如果你想局部启用,可以加在 handler 或者 controller 上:
@Controller('father')
@UseFilters(FatherFilter)
export class FatherController {}
@Get()
@UseFilters(FatherFilter)
findAll() {}
接下来进一步完善内部逻辑,@Catch 指定要捕获的异常,这里指定 BadRequestException
。
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
} from '@nestjs/common';
import { Response } from 'express';
@Catch(BadRequestException)
export class FatherFilter implements ExceptionFilter {
catch(exception: BadRequestException, host: ArgumentsHost) {
const http = host.switchToHttp();
const response = http.getResponse<Response>();
const statusCode = exception.getStatus();
response.status(statusCode).json({
code: statusCode,
message: exception.message,
extra: {},
success:false
});
}
}
通过下面的代码抛出异常,这个异常就会被FatherFilter
捕获,进入到catch
函数逻辑里面,从而返回我们自定义的格式。
@Get()
findAll() {
throw new BadRequestException('xxxxx');
return this.fatherService.findAll();
}
但我们只是 @Catch 了 BadRequestException
如果抛的是其他异常,依然是原来的格式,比如我抛出throw new BadGatewayException('xxxxx');
,我们自定义的exception filter
并没有被捕获到这个错误。
其实,我们只要 @Catch 指定 HttpException
就行了。因为 BadRequestExeption
、BadGateWayException
等都是它的子类。
@Catch(HttpException)
export class FatherFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const http = host.switchToHttp();
const response = http.getResponse<Response>();
const statusCode = exception.getStatus();
response.status(statusCode).json({
code: statusCode,
message: exception.message,
extra: {},
success:false
});
}
}
通过上面的了解,我们知道nest
是如何捕获错误的,但是这远远不够,下面我将讲解在实际项目中如何进行异常的捕获。
实际项目异常处理示例
一般,我们分别要处理统一异常与 HTTP
类型的接口相关异常。
统一异常你可以理解为一个兜底的错误处理。示例代码如下:
import { FastifyReply, FastifyRequest } from "fastify";
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpStatus,
ServiceUnavailableException,
HttpException,
} from '@nestjs/common';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: Error, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<FastifyReply>();
const request = ctx.getRequest<FastifyRequest>();
request.log.error(exception)
// 非 HTTP 标准异常的处理。
response.status(HttpStatus.SERVICE_UNAVAILABLE).send({
statusCode: HttpStatus.SERVICE_UNAVAILABLE,
timestamp: new Date().toISOString(),
path: request.url,
message: new ServiceUnavailableException().getResponse(),
});
}
}
可以看到,Catch
的参数为空时,默认捕获所有异常。new ServiceUnavailableException()
表示该接口服务不可用,作为兜底处理。
处理HTTP
类型的接口相关异常时, Catch
的参数为 HttpException
将只捕获 HTTP
相关的异常错误。
import { FastifyReply, FastifyRequest } from "fastify";
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
} from '@nestjs/common';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<FastifyReply>();
const request = ctx.getRequest<FastifyRequest>();
const status = exception.getStatus();
response.status(status).send({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.getResponse(),
});
}
}
定义好这两个exception filter
文件后,在 main.ts
文件中添加 useGlobalFilters
全局过滤器:
app.useGlobalFilters(new AllExceptionsFilter(), new HttpExceptionFilter());
这里一定要注意引入自定义异常的先后顺序,不然异常捕获逻辑会出现混乱。
验证一下,当访问一个不存在的接口:
验证完 HTTP
异常之后,我们接着在 UserController
中伪造一个程序运行异常的接口,来验证常规异常是否能被正常捕获:
@Get('findError')
findError() {
const a: any = {};
console.log(a.b.c);
return this.userService.findAll();
}
除了全局异常拦截处理之外,我们需要再新建一个 business.exception.ts
来处理业务运行中预知且主动抛出的异常:
首先,我们自定义一个Exception
:
import { HttpException, HttpStatus } from '@nestjs/common';
import { BUSINESS_ERROR_CODE } from './business.error.codes';
type BusinessError = {
code: number;
message: string;
};
export class BusinessException extends HttpException {
constructor(err: BusinessError | string) {
if (typeof err === 'string') {
err = {
code: BUSINESS_ERROR_CODE.COMMON,
message: err,
};
}
super(err, HttpStatus.OK);
}
static throwForbidden() {
throw new BusinessException({
code: BUSINESS_ERROR_CODE.ACCESS_FORBIDDEN,
message: '抱歉哦,您无此权限!',
});
}
}
然后再定义一些错误码:
export const BUSINESS_ERROR_CODE = {
// 公共错误码
COMMON: 10001,
// 特殊错误码
TOKEN_INVALID: 10002,
// 禁止访问
ACCESS_FORBIDDEN: 10003,
// 权限已禁用
PERMISSION_DISABLED: 10003,
// 用户已冻结
USER_DISABLED: 10004,
};
简单改造一下 HttpExceptionFilter
,在处理 HTTP
异常返回之前先处理业务异常:
import { FastifyReply, FastifyRequest } from "fastify";
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { BusinessException } from "./business.exception";
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<FastifyReply>();
const request = ctx.getRequest<FastifyRequest>();
const status = exception.getStatus();
// 处理业务异常
if (exception instanceof BusinessException) {
const error = exception.getResponse();
response.status(HttpStatus.OK).send({
data: null,
status: error['code'],
extra: {},
message: error['message'],
success: false,
});
return;
}
response.status(status).send({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.getResponse(),
});
}
}
完成配置之后,我们继续在 UserController
中重新伪造一个业务异常的场景:
@Get('findBusinessError')
findBusinessError() {
const a: any = {};
try {
console.log(a.b.c);
} catch (error) {
throw new BusinessException('你这个参数错了');
}
return this.userService.findAll();
}
总结
nestjs
处理了三类错误:
-
业务错误:这个是我们主动抛出的错误,通过自定义一个
exception
以及特殊的错误码来完成,这类错误主要是方便我们排查业务。 -
HTTP类错误:主要是前端访问404,访问无权限,前端参数错误等错误。
-
代码逻辑错误:这类错误主要是程序员写的代码有问题,一般不会主动抛出,因此需要程序有一个兜底的捕获机制,防止进程挂掉。