前端异常的捕获与处理

257 阅读4分钟

《用得上的前端知识》系列 - 你我都很忙,能用100字说清楚,绝不写万字长文

异常分类

异常按照捕获方式分类

  • 运行时异常
    • 代码错误
    • 语法错误(无法捕获,会导致当前代码块运行终止)
  • 资源加载异常
  • 异步回调异常
  • async/await异常
  • Promise异常
  • 框架内部异常(如,react、vue)

异常捕获的方法

1、try-catch

将可能引发异常的代码放到 try 中即可。注意,try-catch 只能捕获同步代码运行时产生的代码错误,以下几种情况无法捕获

  • 语法错误(如,代码中使用了中文的分号);
  • 异步错误;
  • promise 中产生的错误。

2、window.onerror & element.onerror

JS 运行时如果发生错误,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()。但请注意,

  • window.onerror 只能捕获到同步代码运行时产生的代码错误,无法捕获静态资源加载异常和 JS 语法错误;
  • 语法错误会导致当前代码块运行终止,如果错误监控语句块中出现语法错误,那么我们就什么也监控不到了;

当一项资源(如、。

注意

不同源的 js 资源,window.onerror 捕获到的错误只有“Script error”,没有更多的错误信息。解决方法(需同时满足):

  • 在 script 标签中添加 crossorigin 属性;
  • 在 js 资源的响应头中添加 Access-Control-Allow-Origin: *。

3、使用 window.addEventListener 监听 error 事件

此方法既能捕获运行时产生的代码错误,也能捕获资源加载产生的异常。资源加载失败不会冒泡,所以我们可以指定在加载失败事件的捕获阶段捕获该错误,如下所示:

window.addEventListener("error", function(e) {}, true); 

4、使用 window.addEventListener 监听 unhandledrejection 事件

当 Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件。该事件用于全局捕获 promise 对象没有 rejection 处理器时异常情况。

window.addEventListener('unhandledrejection', (e) => {
  console.log(`Promise.reject()中的内容,告诉你发生错误的原因:${e.reason}`);
  console.log(`Promise对象 :${e.promise}`);
});

5、promise.then().catch( callback )

异常捕获总结

异常类型同步方法异步回调资源加载promiseasync/await
try/catchYY
window.onerrorYY
addEventListener监听error事件YYY
addEventListener监听unhandledrejection事件YY
  • 运行时异常(代码错误)
    • try-catch;
    • window.onerror;
    • window.addEventListener('error')。
  • 资源加载异常
    • 使用 window.addEventListener 在捕获阶段捕获错误(addEventListener 的第3个参数设为true);
    • element.onerror(代码入侵性太强,每个标签都要添加 onerror 方法)。
  • 异步回调异常
    • window.onerror;
    • addEventListener('error', ()=>{})。
  • Promise 异常
    • 使用 window.addEventListener 监听 unhandledrejection 事件;
    • Promise.then() .catch( callback );
  • async/await 异常
    • 实际上 async/await 语法本质还是 Promise 语法,区别就是 async 方法可以被上层的 try/catch 捕获。
  • 框架内部异常(react、vue)
    • react,使用错误边界组件捕获其子组件中的 JavaScript 错误;
    • vue,通过 vue.config.errorHandler 捕获的错误;
// React 错误边界使用举例
// 创建 ErrorBoundary 组件

export default class ErrorBoundary extends React.Component{
    constructor( props ){
        super(props);
    }

    componentDidCatch(error, info){
        console.log('error:', error);
    }

    render(){
        return this.props.children;
    }
}

// 使用
ReactDOM.render(
    <ErrorBoundary>
        <App />
    </ErrorBoundary>
    , document.getElementById('root')
);

异常捕获的最佳实践

// 异常(错误)监控
window.addEventListener('error', (e)=>{
  console.log('进行错误上报:', e);
}, true);

// 将unhandledrejection事件捕获的错误抛出交由错误事件统一处理
window.addEventListener("unhandledrejection", (e) => {
  throw e.reason;
});

异常上报

上报方式

  • 用一个队列存放错误信息,达到一定的数量(或者一定的时长)统一发送;
  • 发送的动作也可以放在 web worker 中进行,以提高页面性能;

当然,市面上有好多成熟的方案可以选择,比如 Sentry,可以使用开源的版本,结合业务做一些改造:

  • 与构建系统结合,构建项目时自动生成 Sentry 项目,注入 Sentry 脚本;
  • 客服端注入 Sentry 客户端脚本后,按项目、页面等不同粒度配置告警事件的过滤规则;
  • 对接各种消息系统(如,企业微信),将告警消息推送到订阅群。

扩展

1、如何在浏览器中检查页面是否崩溃?

  • Service worker,简称 SW(独立于页面,页面JS线程挂掉后,不会影响其工作)
    • 主页面定期(比如每隔 5s )向 SW 发送请求,SW 记录每次请求的时间和页面;
    • SW 每 10s 检查一下时间间隔是否超过指定时间,若超过,则表示页面已经崩溃。
  • 使用 localStorage
    • 进入页面时设置 localStorage.setItem('crash', '{"/":true}')
    • 离开页面时 (onbeforeunload) 设置 localStorage.setItem('crash', '{"/":false}') // 页面崩溃时不会触发 onbeforeunload 事件。

参考资料