在前端异常处理是非常重要的,包括客户端和服务端的异常。之前异常处理是对于每个异步函数添加err处理,这样不仅加大了工作量,还容易遗漏某些异常。幸好Angular6提供了ErrorHandler来处理异常(Ionic4为IonicErrorHandler),默认的ErrorHandler处理异常是将其输出在console上,这显然不能满足需求,所以需要自己实现一个GlobalErrorHandler。
为什么要用ErrorHandler统一处理异常
- 提高效率(例如以前对于每个异步函数都需要传入err参数来处理,现在用ErrorHandler统一处理)
之前对于异步异常处理的代码是下面这样的,光看到这么多err都会觉得头晕,更何况上面的逻辑代码,当然,对于多个异步请求这并不是最好的处理方式,这里只讨论异常。
(err: any) => {
reject(err);
}
), (err: any) => {
reject(err);
};
}, (err: any) => {
reject(err);
});
},
(err: any) => {
reject(err);
}
);
可以捕获到不易复现的异常,尤其是客户端不易复现的问题。如果客户出现的问题,本地复现不了,又恰好忘了捕获异常,那将会是非常糟糕的,统一处理可将异常信息输入到日志中,能够快速定位问题。
处理不常见的异常或者运行时异常,及时地给用户提示,不至于因为没有捕获异常而导致程序崩溃或者直接将异常暴露给用户,这也是非常糟糕的。
对于用户来说,可以显示统一的、友好的异常信息提示,形成独特的系统风格。对于某些异常,甚至可以向用户解释为何会产生这个异常并引导用户行为去消除这个异常。
比如用户在支付时抛出余额不足异常,这个时候应当提示用户的余额不足之外,还应当显示充值入口(如果当前系统支持充值)或者提示用户去哪里充值。
用ErrorHandler捕获异常的具体实现
第一步:创建ErrorService和LoggingService用于获取异常信息和记录异常日志
ErrorService:
获取客户端的异常信息和堆栈、服务端的异常信息和状态码。
import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
@Injectable()
export class ErrorService {
getClientMessage(error: Error): string {
if (!navigator.onLine) {
return '暂无网络,请检查网络';
}
return error.message ? error.message : error.toString();
}
getClientStack(error: Error): string {
return error.stack;
}
getServerMessage(error: HttpErrorResponse): string {
return error.message;
}
getServerStatus(error: HttpErrorResponse): number {
return error.status;
}
}
LoggingService:
通过Beacon API向后端发送异常信息,记录异常日志,包括当前登录用户,异常发生时间,异常信息等。
import { Injectable } from '@angular/core';
import { DatePipe } from '@angular/common';
import { GlobalProvider } from '../globalprovider';
import { environment } from '@env/environment';
import { URL } from '../url';
@Injectable()
export class LoggingService {
constructor(private global: GlobalProvider,
private datePipe: DatePipe) { }
logError(message: string, stack: string) {
let currentUser = 'notLogin';
if (this.global.isLogin) {
currentUser = this.global.currentUser.username;
}
const data = `{user: ${currentUser}, logtime: ${this.datePipe.transform(new Date(), 'yyyy-MM-dd HH:mm:ss')}, content: ${message}, stack: ${stack}}`;
if (navigator.sendBeacon) {
navigator.sendBeacon(environment.SERVER_URL + URL.log, data);
} else {
// 不支持Beacon
const xhr = new XMLHttpRequest();
xhr.open('post', environment.SERVER_URL + URL.log, false);
xhr.send(data);
}
}
}
第二步:创建GlobalErrorHandler
- 创建global-error-handler.ts
import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor(private injector: Injector) { }
handleError(error: Error | HttpErrorResponse) {
}
}
- 在app.module.ts中添加
providers: [
...
{ provide: ErrorHandler, useClass: GlobalErrorHandler},
...
]
3.添加异常处理逻辑
- 当前使用的框架是Ant Design,所以用户提示就用了NzMessageService。
- 当程序中出现异常时,会自动调用handleError钩子,可判断是客户端还是来自服务端的异常。
- 在实际项目中,可能需要提供显示异常信息的Service来满足不同异常信息的显示。
- 对于异常日志的记录,可根据异常的内容来筛选,不需要每个异常都记录。
import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { LoggingService } from './services/logging.service';
import { ErrorService } from './services/error.service';
import { NzMessageService } from 'ng-zorro-antd';
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor(private injector: Injector) { }
handleError(error: Error | HttpErrorResponse) {
const errorService = this.injector.get(ErrorService);
const logger = this.injector.get(LoggingService);
const msg = this.injector.get(NzMessageService);
let message;
let status;
let stackTrace;
if (error instanceof HttpErrorResponse) {
message = errorService.getServerMessage(error);
status = errorService.getServerStatus(error);
// 提示异常信息
msg.error(message);
} else {
message = errorService.getClientMessage(error);
stackTrace = errorService.getClientStack(error);
// 提示异常信息
msg.error(message);
}
// 记录日常日志
logger.logError(message, stackTrace);
}
}
注:很多人之前在Interceptor中处理异常,Interceptor中只能处理HttpErrorResponse类型的error,如果这里处理了,那么ErrorHandler将捕获不到。所以在Interceptor中,遇到HttpErrorResponse的error需要抛出。
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
// 跳到登录页或者刷新token
} else {
return throwError(error);
}
})
对于一个项目,有一个良好的异常处理机制是非常重要的,ErrorHandler解决了很多之前异常处理方式的痛点,对于使用Angular/Ionic的小伙伴来说是个不错的选择。