前端异常监控
目的:及时感知线上问题,减少异常对用户的影响。
在JS中,异常不会导致JS线程崩溃,最多只会让当前执行程序提前终止。
-
当前代码块将作为一个任务压入任务队列中,JS 线程会不断地从任务队列中提取任务执行。
-
当任务执行过程中出现异常,且异常没有捕获处理,则会一直沿着调用栈一层层向外抛出,最终终止当前任务的执行。
-
JS 线程会继续从任务队列中提取下一个任务继续执行。
错误监控的分类:
a. 脚本错误监控
b. 请求错误监控
c. 资源错误监控
脚本错误
脚本错误,可分为编译时的错误和运行时错误。编译时错误,可以通过lint(如eslint和tslint)以及git提交插件husky,基本保障提交到线上的代码不出现低级的编译时错误。
而发现并上报运行时错误就是前端检测平台的本质工作啦,一般来说,脚本错误监控指的就是运行时错误监控。
1. try... catch...
说到脚本错误监控,我们可能最先想到的是try ... catch ... 。try... catch... 语法块确实可以帮助我们帮助我们捕获异常,但仅仅是非异步的错误;并且这种捕获是侵入式的,仅限于可预见的异常。要对这个系统进行异常监控,这样方法并非最佳选择,我们需要全局的异常捕获。
如果要在异步中使用try... catch... ,则要把try... catch... 发在异步的内部
setTimeout(() => {
try {
error;
} catch (e) {
console.log('error捕获', e);
}
}, 1000);
2. onerror事件(冒泡阶段)
当页面出现脚本错误时,就会产生 onerror 事件,我们只需捕获该事件即可。这种捕获方法可以捕获到同步和异步错误,并且这种捕获的错误不再是可预见的。
* @description window.onerror 全局捕获错误 * @param event 错误信息,如果是 * @param source 错误源文件URL * @param lineno 行号 * @param colno 列号 * @param error Error对象 */ window.onerror = function (event, source, lineno, colno, error) { // 上报错误 // 如果不想在控制台抛出错误,只需返回 true 即可
console.log('我知道错误了');
console.log({ msg, url, row, col, error })
return true;
};
需要注意的是,
- window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使知道异常的发生,控制台还是会显示。
- 为了达到全局捕获,我们通常将onerror定义在所有JS 脚本的前面。(如果写在后面,一旦发生错误的话是不会被 onerror 捕获到的。)
- onerror无法捕获到网络异常。
3. addEventListener
/** * @param event 事件名 * @param function 回调函数 * @param useCapture 回调函数是否在捕获阶段执行,默认是false,在冒泡阶段执行 */ window.addEventListener('error', (event) => { // addEventListener 回调函数的离散参数全部聚合在 error 对象中 // 上报错误 }, true)
相比于onerror, addEventListener可以绑定多个回调函数.此外还可以指定错误捕获的时机是冒泡阶段(默认false)还是捕获阶段(true)
4. Promise错误捕获:上述方法(try catch, onerror, addEventListener)都无法捕获Promise异常。
对于未处理的Promise异常,会触发unhandledrejection事件。
对于处理过的Promise异常,会触发rejectionhandled事件。
window.addEventListener('error', (msg, url, row, col, error) => {
console.log('我感知不到 promise 错误');
console.log(
msg, url, row, col, error
);
}, true);
Promise.reject('promise error');
new Promise((resolve, reject) => {
reject('promise error');
});
new Promise((resolve) => {
resolve();
}).then(() => {
throw 'promise error'
})

所以官方总是建议我们在Promise的最后加上catch(错误被捕获,不会使程序终止)
new Promise((resolve) => { resolve(); }).then(() => { throw 'promise error' }).catch((error) => { console.log(error); })

请求错误
由于网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行,但是这种方式虽然可以捕捉到网络请求的异常,但是无法判断 HTTP 的状态是 404 还是其他比如 500 等等,所以还需要配合服务端日志才进行排查分析才可以。
资源错误
资源错误监控本质上和常规脚本错误监控一样,都是监控错误事件实现错误捕获。
那么如果区分脚本错误还是资源错误呢?我们可以通过 instanceof 区分,脚本错误参数对象 instanceof ErrorEvent,而资源错误的参数对象 instanceof Event。
由于 ErrorEvent 继承于 Event ,所以不管是脚本错误还是资源错误的参数对象,它们都 instanceof Event,所以,需要先判断脚本错误。
此外,两个参数对象之间有一些细微的不同,比如,脚本错误的参数对象中包含 message ,而资源错误没有,这些都可以作为判断资源错误或者脚本错误的依据。
/** * @param event 事件名 * @param function 回调函数 * @param useCapture 回调函数是否在捕获阶段执行,默认是false,在冒泡阶段执行 */ window.addEventListener('error', (event) => { if (event instanceof ErrorEvent) { console.log('脚本错误') } else if (event instanceof Event) { console.log('资源错误') } }, true);
使用 addEventListener 捕获资源错误时,一定要将 useCapture 即第三个选项设为 true,因为资源错误没有冒泡,所以只能在捕获阶段捕获。同理,由于 window.onerror 是通过在冒泡阶段捕获错误,所以无法捕获资源错误。

浙公网安备 33010602011771号