为何要处理异常?
提升用户体验,及早发现/定位问题(比如机型,系统,移动端某些无法复现的问题)
常见异常种类
- js 语法错误
- ajax 请求报错
- 静态资源加载异常
- promise 异常
- iframe 异常
- 跨域 Script Error
- 崩溃/卡顿
异常处理
- try...catch:
- 只能捕捉到同步的运行时错误
// 同步错误 ok
try {
let name = 'name';
console.log(nam);
} catch (err) {
console.log(err);
}
- 不能捕捉到语法错误,不能捕捉到异步错误
try {
// 语法错误 not ok
let name = 'jartto;
console.log(nam);
// 异步错误 not ok
// setTimeout(() => {
// undefined.map(v => v);
// }, 1000)
} catch(e) {
console.log('捕获到异常:',e);
}
- window.onerror:当 JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()。
- 可捕捉:同步错误,异步错误
- 不可捕捉:语法错误,静态资源错误,接口异常
注意:- window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生控制台还是会显示 Uncaught Error: xxxxx
- 必须放在所有脚本前面才能捕获错误
// @param {String} message 错误信息
// @param {String} source 出错文件
// @param {Number} lineno 行号
// @param {Number} colno 列号
// @param {Object} error Error 对象(对象)
window.onerror = function (message, source, lineno, colno, error) {
console.log('捕获到异常:', { message, source, lineno, colno, error });
return true;
};
// 同步 ok
throw new Error('error');
// 异步 ok
setTimeout(() => {
throw new Error('error');
});
// 语法错误 not ok
let name = 'name
// 网络异常 not ok
let img = new Image()
img.src = './img.png'
-
window.addEventListener
当一项资源(如图片或脚本)加载失败,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的 onerror() 处理函数。这些 error 事件不会向上冒泡到 window ,不过(至少在 Firefox 中)能被单一的 window.addEventListener 捕获。
注意:- 网络请求不会冒泡,所以需要在捕获阶段捕捉到,但是无法判断 HTTP 状态码,需要配合服务器日志排查
- 避免重复监听/注意不同浏览器的兼容处理
window.addEventListener( 'error', error => { console.log('捕获到异常:', error); }, true, ); let img = new Image(); img.src = './img.png'; img.onload = function (e) { console.log(e); }; img.onerror = function (e) { console.log(e); }; document.body.appendChild(img);
-
Promise Catch
在 promise 中可以用 catch 捕捉错误,没有被 catch 的错误也无法被 onerror 或 try-catch 捕获到,为了防止部分 promise 错误被漏掉,在全局增加一个 unhandledrejecttion 处理window.addEventListener('unhandledrejection', function (e) { e.preventDefault(); // 去掉控制台错误显示 console.log('捕获到异常:', e); return true; }); Promise.reject('promise error');
Vue errorHandler
捕捉计算属性/方法运行时错误
Vue.config.errorHandler = (err, vm, info) => {
console.error('通过vue errorHandler捕获的错误');
console.error(err);
console.error(vm);
console.error(info);
};
- React componentDidCatch
componentDidCatch(error, info) {
console.log(error, info);
}
- iframe 异常 借助 window.onerror
- script error
// 跨源资源共享机制( CORS ):我们为 script 标签添加 crossOrigin 属性。
;
// 动态添加脚本
const script = document.createElement('script');
script.crossOrigin = 'anonymous';
script.src = url;
document.body.appendChild(script);
(() => {
const originAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, listener, options) {
// 捕获添加事件时的堆栈
const addStack = newError(`Event (${type})`).stack;
const wrappedListener = function (...args) {
try {
return listener.apply(this, args);
}
catch (err) {
// 异常发生时,扩展堆栈
err.stack += '\n' + addStack;
throw err;
}
}
return originAddEventListener.call(this, type, wrappedListener, options);
}
})();
- 崩溃和卡顿: window 的 load 和 beforeunload 或者 service worker
- 在网页加载后,不断更新 session 中的时间,在登出后,将登出态设置为正常登出
- 判断上次是否是正常登出,获取最后一次时间
// 使用定时器
window.addEventListener('load', function () {
sessionStorage.setItem('good_exit', 'pending');
setInterval(function () {
sessionStorage.setItem('time_before_crash', newDate().toString());
}, 1000);
});
window.addEventListener('beforeunload', function () {
sessionStorage.setItem('good_exit', 'true');
});
if (sessionStorage.getItem('good_exit') && sessionStorage.getItem('good_exit') !== 'true') {
/*
insert crash logging code here
*/
alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));
}
- 错误上报
- ajax 发送数据,但是 ajax 也可能发生异常
- 动态创建 img
function report(error) {
let reportUrl = 'http://jartto.wang/report';
new Image().src = `${reportUrl}?logs=${error}`;
}
3. 优化: 过多的错误可能导致崩溃, 采集率
Reporter.send = function (data) {
// 只采集 30%
if (Math.random() < 0.3) {
send(data); // 上报错误信息
}
};
总结
异常处理
- try...catch: 可疑区域监控(同步错误)
- window.onerror : 全局 js 监控异常(同步/异步错误)
- window.addEventListener: 全局监控静态资源异常(网络请求/同步/异步错误)
- unhandledrejection: 捕获未 catch 的异常
- VUE errorHandler 和 React componentDidCatch
- window.load 和 window.beforeunload :监控网页崩溃
- 跨域:crossOrigin
参考:https://mp.weixin.qq.com/s/prf-mXexBh1Ie-ctq9FnzA
参考:http://jartto.wang/2018/11/20/js-exception-handling/