《用得上的前端知识》系列 - 你我都很忙,能用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 )
异常捕获总结
| 异常类型 | 同步方法 | 异步回调 | 资源加载 | promise | async/await |
|---|---|---|---|---|---|
| try/catch | Y | Y | |||
| window.onerror | Y | Y | |||
| addEventListener监听error事件 | Y | Y | Y | ||
| addEventListener监听unhandledrejection事件 | Y | Y |
- 运行时异常(代码错误)
-
- 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 事件。