其他
如何优秀的处理前端异常?
你的关注意义重大!
作者: Jartto
http://jartto.wang/2018/11/20/js-exception-handling/
一、为什么要处理异常?
增强用户体验; 远程定位问题; 未雨绸缪,及早发现问题; 无法复线问题,尤其是移动端,机型,系统都是问题; 完善的前端方案,前端监控系统;
二、需要处理哪些异常?
JS 语法错误、代码异常 AJAX 请求异常 静态资源加载异常 Promise 异常 Iframe 异常 跨域 Script error 崩溃和卡顿
三、Try-Catch 的误区
同步运行时错误:
let name = 'jartto';
console.log(nam);
} catch(e) {
console.log('捕获到异常:',e);
}
at <anonymous>:3:15
不能捕获到具体的语法错误,只有一个语法错误提示。我们修改一下代码,删掉一个单引号:
let name = 'jartto;
console.log(nam);
} catch(e) {
console.log('捕获到异常:',e);
}
不过语法错误在我们开发阶段就可以看到,应该不会顺利上到线上环境。
异步错误
setTimeout(() => {
undefined.map(v => v);
}, 1000)
} catch(e) {
console.log('捕获到异常:',e);
}
at setTimeout (<anonymous>:3:11)
四、window.onerror 不是万能的
* @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});
}
首先试试同步运行时错误
// message:错误信息(字符串)。
// source:发生错误的脚本URL(字符串)
// lineno:发生错误的行号(数字)
// colno:发生错误的列号(数字)
// error:Error对象(对象)
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
Jartto;
再试试语法错误呢?
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
let name = 'Jartto
怀着忐忑的心,我们最后来试试异步运行时错误:
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
setTimeout(() => {
Jartto;
});
at setTimeout (http://127.0.0.1:8001/:36:5)}
接着,我们试试网络请求异常的情况:
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
return true;
}
</script>
<img src="./jartto.png">
我们发现,不论是静态资源异常,或者接口异常,错误都无法捕获到。
console.log('捕获到异常:',{message, source, lineno, colno, error});
return true;
}
setTimeout(() => {
Jartto;
});
at setTimeout ((index):36)
onerror 最好写在所有 JS 脚本的前面,否则有可能捕获不到错误; onerror 无法捕获语法错误;
五、window.addEventListener
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
</script>
<img src="./jartto.png">
不同浏览器下返回的 error 对象可能不同,需要注意兼容处理。 需要注意避免 addEventListener 重复监听。
六、Promise Catch
console.log(e);
});
e.preventDefault()
console.log('捕获到异常:', e);
return true;
});
Promise.reject('promise error');
e.preventDefault()
console.log('捕获到异常:', e);
return true;
});
new Promise((resolve, reject) => {
reject('jartto: promise error');
});
VUE errorHandler
console.error('通过vue errorHandler捕获的错误');
console.error(err);
console.error(vm);
console.error(info);
}
console.log(error, info);
}
事件处理器 异步代码 服务端的渲染代码 在 error boundaries 区域内的错误
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
<MyWidget />
</ErrorBoundary>
九、iframe 异常
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
<script>
window.frames[0].onerror = function (message, source, lineno, colno, error) {
console.log('捕获到 iframe 异常:',{message, source, lineno, colno, error});
return true;
};
</script>
十、Script error
script.crossOrigin = 'anonymous';
script.src = url;
document.body.appendChild(script);
EventTarget.prototype.addEventListener = function (type, listener, options) {
const wrappedListener = function (...args) {
try {
return listener.apply(this, args);
}
catch (err) {
throw err;
}
}
return originAddEventListener.call(this, type, wrappedListener, options);
}
const originAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, listener, options) {
+ // 捕获添加事件时的堆栈
+ const addStack = new Error(`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 事件实现了网页崩溃的监控。不错的文章,推荐阅读:Logging Information on Browser Crashes。
sessionStorage.setItem('good_exit', 'pending');
setInterval(function () {
sessionStorage.setItem('time_before_crash', new Date().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'));
}
基于以下原因,我们可以使用 Service Worker 来实现网页崩溃的监控:
十二、错误上报
let reportUrl = 'http://jartto.wang/report';
new Image().src = `${reportUrl}?logs=${error}`;
}
// 只采集 30%
if(Math.random() < 0.3) {
send(data) // 上报错误信息
}
}
十三、总结
可疑区域增加 Try-Catch 全局监控 JS 异常 window.onerror 全局监控静态资源异常 window.addEventListener 捕获没有 Catch 的 Promise 异常:unhandledrejection VUE errorHandler 和 React componentDidCatch 监控网页崩溃:window 对象的 load 和 beforeunload 跨域 crossOrigin 解决
— 完 —
❤️ 看完两件事
如果你觉得这篇内容对你有所帮助,我想邀请你帮我两个小忙:
点个「
在看
」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)关注公众号「IT平头哥联盟」,一起进步,一起成长!
推荐阅读:
2020 校招,我是如何拿到小米、京东、字节大厂前端offer
学习 sentry 源码架构,打造属于自己的前端异常监控平台