為何要處理異常?
提升用戶體驗舀射,及早發(fā)現(xiàn)/定位問題(比如機型怀伦,系統(tǒng)房待,移動端某些無法復(fù)現(xiàn)的問題)
常見異常種類
- js 語法錯誤
- ajax 請求報錯
- 靜態(tài)資源加載異常
- 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:當(dāng) JS 運行時錯誤發(fā)生時拜鹤,window 會觸發(fā)一個 ErrorEvent 接口的 error 事件署惯,并執(zhí)行 window.onerror()镣隶。
- 可捕捉:同步錯誤,異步錯誤
- 不可捕捉:語法錯誤轻猖,靜態(tài)資源錯誤域那,接口異常
注意:- window.onerror 函數(shù)只有在返回 true 的時候,異常才不會向上拋出败许,否則即使是知道異常的發(fā)生控制臺還是會顯示 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
// 網(wǎng)絡(luò)異常 not ok
let img = new Image()
img.src = './img.png'
-
window.addEventListener
當(dāng)一項資源(如圖片或腳本)加載失敗市殷,加載資源的元素會觸發(fā)一個 Event 接口的 error 事件刹衫,并執(zhí)行該元素上的 onerror() 處理函數(shù)。這些 error 事件不會向上冒泡到 window 音羞,不過(至少在 Firefox 中)能被單一的 window.addEventListener 捕獲仓犬。
注意:- 網(wǎng)絡(luò)請求不會冒泡搀继,所以需要在捕獲階段捕捉到,但是無法判斷 HTTP 狀態(tài)碼民镜,需要配合服務(wù)器日志排查
- 避免重復(fù)監(jiān)聽/注意不同瀏覽器的兼容處理
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
<iframe src="./iframe.html" frameborder="0"></iframe>
<script>
window.frames[0].onerror = function (message, source, lineno, colno, error) {
console.log('捕獲到 iframe 異常:',{message, source, lineno, colno, error});
return true;
};
</script>
- script error
// 跨源資源共享機制( CORS ):我們?yōu)?script 標(biāo)簽添加 crossOrigin 屬性馋嗜。
<script src='http://jartto.wang/main.js' crossorigin></script>;
// 動態(tài)添加腳本
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) {
// 異常發(fā)生時葛菇,擴展堆棧
err.stack += '\n' + addStack;
throw err;
}
}
return originAddEventListener.call(this, type, wrappedListener, options);
}
})();
- 崩潰和卡頓: window 的 load 和 beforeunload 或者 service worker
- 在網(wǎng)頁加載后,不斷更新 session 中的時間济舆,在登出后莺债,將登出態(tài)設(shè)置為正常登出
- 判斷上次是否是正常登出齐邦,獲取最后一次時間
// 使用定時器
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 發(fā)送數(shù)據(jù),但是 ajax 也可能發(fā)生異常
- 動態(tài)創(chuàng)建 img
function report(error) {
let reportUrl = 'http://jartto.wang/report';
new Image().src = `${reportUrl}?logs=${error}`;
}
3. 優(yōu)化: 過多的錯誤可能導(dǎo)致崩潰我纪, 采集率
Reporter.send = function (data) {
// 只采集 30%
if (Math.random() < 0.3) {
send(data); // 上報錯誤信息
}
};
總結(jié)
異常處理
- try...catch: 可疑區(qū)域監(jiān)控(同步錯誤)
- window.onerror : 全局 js 監(jiān)控異常(同步/異步錯誤)
- window.addEventListener: 全局監(jiān)控靜態(tài)資源異常(網(wǎng)絡(luò)請求/同步/異步錯誤)
- unhandledrejection: 捕獲未 catch 的異常
- VUE errorHandler 和 React componentDidCatch
- window.load 和 window.beforeunload :監(jiān)控網(wǎng)頁崩潰
- 跨域:crossOrigin
參考:https://mp.weixin.qq.com/s/prf-mXexBh1Ie-ctq9FnzA
參考:http://jartto.wang/2018/11/20/js-exception-handling/