1.try-catch
1. 執行時錯誤
try {
fn()
} catch (e) {
console.log(e);
}
2. 非同步錯誤
try {
setTimeout(() => {
fn() // 非同步錯誤
})
} catch(e) {
console.log('我感知不到錯誤');
console.log(e);
}
複製程式碼
總結: 只能捕獲捉到執行時非非同步錯誤,非同步錯誤就顯得無能為力,捕捉不到
2. window.onerror
1.同步錯誤
/**
* @param {String} msg 錯誤資訊
* @param {String} url 出錯檔案
* @param {Number} row 行號
* @param {Number} col 列號
* @param {Object} error 錯誤詳細資訊
*/
window.onerror = function(msg, url, row, col, error) {
console.log('我知道錯誤了');
return true;
};
new Error();
2.非同步錯誤
window.onerror = function(msg, url, row, col, error) {
console.log('我知道非同步錯誤了');
return true;
};
setTimeout(() => {
new Error();;
});
複製程式碼
注意:在事件處理程式中返回false,可以阻止瀏覽器報告錯誤的預設行為
window.onerror = function(msg, url, row, col, error) {
return false;
}
複製程式碼
當我們遇到 "img src="./404.png" 報 404 網路請求異常的時候,window.onerror 是無法幫助我們捕獲到異常的。
3.資源載入錯誤
1. object.onerror
2. performance.getEntries()
3. Error
事件捕獲 (全域性監控靜態資源異常)
-
object.onerror
如script,image等標籤src引用,會返回一個
event
物件 ,TIPS:object.onerror
不會冒泡到window
物件,所以window.onerror無法監控資源載入錯誤
var img = new Image();
img.src = 'http://xxx.com/xxx.jpg';
img.onerror = function(event) {
console.log(event);
}
複製程式碼
-
performance.getEntries()
返回已成功載入的資源列表,然後自行做比對差集運算,核實哪些檔案沒有載入成功
var result = [];
window.performance.getEntries().forEach(function (perf) {
result.push({
'url': perf.name,
'entryType': perf.entryType,
'type': perf.initiatorType,
'duration(ms)': perf.duration
});
});
console.log(result);
複製程式碼
3. Error事件捕獲
**404.png**
複製程式碼
window.addEventListener('error', (msg, url, row, col, error) => {
console.log('我知道 404 錯誤了');
console.log(
msg, url, row, col, error
);
return false;
}, true);
複製程式碼
4. 全域性去捕獲promise error
window.addEventListener("unhandledrejection", function(e) {
e.preventDefault()
console.log('我知道 promise 的錯誤了');
console.log(e.reason);
return true;
});
Promise.reject('promise error').catch((err)=>{
console.log(err);
})
new Promise((resolve, reject) => {
reject('promise error');
}).catch((err)=>{
console.log(err);
})
new Promise((resolve) => {
resolve();
}).then(() => {
throw 'promise error'
});
new Promise((resolve, reject) => {
reject(123);
})
複製程式碼
5. 跨域的js錯誤捕獲
一般涉及跨域的js執行錯誤時會丟擲錯誤提示script error
,但沒有具體資訊(如出錯檔案,行列號提示等), 可利用資源共享策略來捕獲跨域js錯誤
- 客戶端:在script標籤增加crossorigin屬性(客戶端)
- 服務端:js資源響應頭
Access-Control-Allow-Origin: *
6. Iframe錯誤
<iframe src="./iframe.html" frameborder="0"></iframe>
<script>
window.frames[0].onerror = function (msg, url, row, col, error) {
console.log('我知道 iframe 的錯誤了,也知道錯誤資訊');
console.log({
msg, url, row, col, error
})
return true;
};
</script>
複製程式碼
7. Node 錯誤監控
- uncaughtException來全域性捕獲未捕獲的Error
未捕獲的 JavaScript 異常一直冒泡回到事件迴圈時,會觸發 'uncaughtException' 事件。 預設情況下,Node.js 通過將堆疊跟蹤列印到 stderr 並使用退出碼 1 來處理此類異常,從而覆蓋任何先前設定的 process.exitCode。 為 'uncaughtException' 事件新增處理程式會覆蓋此預設行為。 或者,更改 'uncaughtException' 處理程式中的 process.exitCode,這將導致程式退出並提供退出碼。 否則,在存在這樣的處理程式的情況下,程式將以 0 退出
process.on("uncaughtException", function(a) {
})
複製程式碼
- unhandledRejection區域性錯誤
如果在事件迴圈的一次輪詢中,一個 Promise 被 rejected,並且此 Promise 沒有繫結錯誤處理器, 'unhandledRejection 事件會被觸發。 當使用 Promise 進行程式設計時,異常會以 "rejected promises" 的形式封裝。Rejection 可以被 promise.catch() 捕獲並處理,並且在 Promise 鏈中傳播。'unhandledRejection 事件在探測和跟蹤 promise 被 rejected,並且 rejection 未被處理的場景中是很有用的。
process.on("unhandledRejection", function(a) {
});
複製程式碼
8. console.error
var consoleError = window.console.error;
window.console.error = function () {
alert(JSON.stringify(arguments)); // 自定義處理
consoleError && consoleError.apply(window, arguments);
};
複製程式碼
9. 介面錯誤
- xmlHttpRequest封裝
if(!window.XMLHttpRequest) return;
var xmlhttp = window.XMLHttpRequest;
var _oldSend = xmlhttp.prototype.send;
var _handleEvent = function (event) {
if (event && event.currentTarget && event.currentTarget.status !== 200) {
// 自定義錯誤上報 }
}
xmlhttp.prototype.send = function () {
if (this['addEventListener']) {
this['addEventListener']('error', _handleEvent);
this['addEventListener']('load', _handleEvent);
this['addEventListener']('abort', _handleEvent);
} else {
var _oldStateChange = this['onreadystatechange'];
this['onreadystatechange'] = function (event) {
if (this.readyState === 4) {
_handleEvent(event);
}
_oldStateChange && _oldStateChange.apply(this, arguments);
};
}
return _oldSend.apply(this, arguments);
}
複製程式碼
- fetch封裝
if(!window.fetch) return;
let _oldFetch = window.fetch;
window.fetch = function () {
return _oldFetch.apply(this, arguments)
.then(res => {
if (!res.ok) { // True if status is HTTP 2xx
// 上報錯誤
}
return res;
})
.catch(error => {
// 上報錯誤
throw error;
})
}
複製程式碼
統計每個頁面的JS和CSS載入時間
在JS或者CSS載入之前打上時間戳,載入之後打上時間戳,並且將資料上報到後臺。載入時間反映了頁面白屏,可操作的等待時間。
<script>var cssLoadStart = +new Date</script>
<link rel="stylesheet" href="xxx.css" type="text/css" media="all">
<link rel="stylesheet" href="xxx1.css" type="text/css" media="all">
<link rel="stylesheet" href="xxx2.css" type="text/css" media="all">
<sript>
var cssLoadTime = (+new Date) - cssLoadStart;
var jsLoadStart = +new Date;
</script>
<script type="text/javascript" src="xx1.js"></script>
<script type="text/javascript" src="xx2.js"></script>
<script type="text/javascript" src="xx3.js"></script>
<script>
var jsLoadTime = (+new Date) - jsLoadStart;
var REPORT_URL = 'xxx/cgi?data='
var img = new Image;
img.onload = img.onerror = function(){
img = null;
};
img.src = REPORT_URL + cssLoadTime + '-' + jsLoadTime;
</script>
複製程式碼
上報頻率
錯誤資訊頻繁傳送上報請求,會對後端伺服器造成壓力。 專案中我們可通過設定採集率,或對規定時間內資料彙總再上報,減少請求數量,從而緩解服務端壓力。
// 借鑑別人的一個例子
Reporter.send=function(data) {
// 只採集30%
if(Math.random() < 0.3) {
send(data); // 上報錯誤
}
}
複製程式碼
異常上報後臺伺服器
- window.onerror
window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error) {
// 構建錯誤物件
var errorObj = {
errorMessage: errorMessage || null,
scriptURI: scriptURI || null,
lineNo: lineNo || null,
columnNo: columnNo || null,
stack: error && error.stack ? error.stack : null
};
if (XMLHttpRequest) {
var xhr = new XMLHttpRequest();
xhr.open('post', '/middleware/errorMsg', true); // 上報給node中間層處理
xhr.setRequestHeader('Content-Type', 'application/json'); // 設定請求頭
xhr.send(JSON.stringify(errorObj)); // 傳送引數
}
}
複製程式碼
- sourceMap解析
const express = require('express');
const fs = require('fs');
const router = express.Router();
const fetch = require('node-fetch');
const sourceMap = require('source-map');
const path = require('path');
const resolve = file => path.resolve(__dirname, file);
// 定義post介面
router.post('/errorMsg/', function(req, res) {
let error = req.body; // 獲取前端傳過來的報錯物件
let url = error.scriptURI; // 壓縮檔案路徑
if (url) {
let fileUrl = url.slice(url.indexOf('client/')) + '.map'; // map檔案路徑
// 解析sourceMap
let smc = new sourceMap.SourceMapConsumer(fs.readFileSync(resolve('../' + fileUrl), 'utf8')); // 返回一個promise物件
smc.then(function(result) {
// 解析原始報錯資料
let ret = result.originalPositionFor({
line: error.lineNo, // 壓縮後的行號
column: error.columnNo // 壓縮後的列號
});
let url = ''; // 上報地址
// 將異常上報至後臺
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
errorMessage: error.errorMessage, // 報錯資訊
source: ret.source, // 報錯檔案路徑
line: ret.line, // 報錯檔案行號
column: ret.column, // 報錯檔案列號
stack: error.stack // 報錯堆疊
})
}).then(function(response) {
return response.json();
}).then(function(json) {
res.json(json);
});
})
}
});
module.exports = router;
複製程式碼
我們何時上報後臺呢?
if (window.requestIdleCallback) {
window.requestIdleCallback()
} else {
setTimeout()
}
複製程式碼
體外活
- Vue 2.x中我們應該這樣捕獲全域性異常:
Vue.config.errorHandler = function (err, vm, info) {
let {
message, // 異常資訊
name, // 異常名稱
script, // 異常指令碼url
line, // 異常行號
column, // 異常列號
stack // 異常堆疊資訊
} = err;
// vm為丟擲異常的 Vue 例項
// info為 Vue 特定的錯誤資訊,比如錯誤所在的生命週期鉤子
}
複製程式碼
- React 16.x 版本中引入了 Error Boundary:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
// 將異常資訊上報給伺服器
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
return '出錯了';
}
return this.props.children;
}
}
複製程式碼