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);
image
-
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;
image
我们何时上报后台呢?
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;
}
}
网友评论