Angular/Ionic 异常处理:ErrorHandler

在前端异常处理是非常重要的,包括客户端和服务端的异常。之前异常处理是对于每个异步函数添加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
  1. 创建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) {
    }
}

  1. 在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的小伙伴来说是个不错的选择。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容