思考前端常见哪些错误?
- 脚本错误
-
语法错误
-
运行时错误
-
同步错误
-
异步错误
-
Promise错误
- 网络错误
- 资源加载错误
- 自定义请求错误
我们最常用的解决错误的方式,你能想到哪些?
- try...catch(e){}
- Promise.reject() / Promise.catch()
try...catch(e){}的问题
- 无法捕获语法错误。
- 可以利用setTimeout捕获异步错误,但是无法捕获其他上下文的错误。
window.onerror()
比 try...catch(e){} 捕获错误信息能力更强大。
/**
* @param {String} msg 错误描述
* @param {String} url 报错文件
* @param {Number} row 行号
* @param {Number} col 列号
* @param {Object} error 错误Error对象
*/
window.onerror = function (msg, url, row, col, error) {
console.log('我知道错误了');
return true; // 返回 true 的时候,异常不会向上抛出,控制台不会输出错误
};
使用注意:
- 应该在所有脚本之前被执行,以免有遗漏。
- 一定要renturn true。否则异常还会向上抛出,出现在控制台上。
优点:
- 可以捕获常见的语法、同步、异步错误等错误。
缺点:
- 无法捕获Promise错误、网络错误。
- 容易被覆盖,可能别人也在使用该事件监听。
原理:
- 利用了事件冒泡,从而达到监听的作用。
网络错误
window.onerror无法捕获网络请求异常的错误,这是因为网络请求异常不会事件冒泡。所以,必须在事件捕获阶段将异常捕捉到。
<script>
window.addEventListener('error', (msg, url, row, col, error) => {
console.log('我知道 404 错误了');
console.log(
msg, url, row, col, error
);
return true;
}, true);
</script>
<img src="./404.png" alt="">
原理:事件捕获阶段将异常捕捉到。
优点:
- 不怕回调被覆盖,可以监听多个回调函数。
缺点:
- 无法知道是404还是500等等。需要服务端的日志进行排查分析才可以。
- 不销毁就会造成内存泄漏。
常见用途:
用户访问网站,图片 CDN 无法服务,图片加载不出来而开发人员没有察觉就尴尬了。
Promise 错误
我们在写Promise的时候,总是会忘记去写catch。导致Promise抛出的异常无法捕获,哪怕你使用try...catch(e){} 或 onerror也无能为力。
所以,在用到很多Promise实例时,最好添加一个 Promise 全局异常捕获事件:unhandledrejection。
window.addEventListener("unhandledrejection", function(e){
e.preventDefault()
console.log('我知道 promise 的错误了');
console.log(e.reason);
return true;
});
Promise.reject('promise error');
new Promise((resolve, reject) => {
reject('promise error');
});
new Promise((resolve) => {
resolve();
}).then(() => {
throw 'promise error'
});
上报方式
客户端监听到异常需要上报给服务端,服务端将错误详情整合,进行响应。
方式:
- 动态的创建img标签,请求长度有限制。
- ajax
使用ajax类库,在抛出异常监听的之后请求,发送给服务端。
Script error 脚本错误是什么?
原因是引用了跨域脚本,浏览器处于安全考虑,不显示具体错误而是 Script error。
解决:
-
所有资源切换到同一域名,这样会失去CDN优势。
-
脚本配置crossorigin属性,服务端设置CORS。
-
script:
<script src="http://www.xxx.com/index.js" crossorigin></script> -
服务端:
Access-Control-Allow-Origin: You-allow-origin
crossorigin属性取值和响应头:
-
crossorigin="anonymous"(默认),CORS不等于You-allow-origin,不能带cookie -
crossorigin="use-credentials"且Access-Control-Allow-Credentials: true,CORS不能设置为*,能带cookie。如果CORS不等于You-allow-origin,浏览器不加载 js。
压缩代码如何定位到脚本异常位置?
问题就想标题一样明确了,但还是想举个例子:
源代码(存在错误):
function test() {
noerror // <- 报错
}
test();
经 webpack 打包压缩后产生如下代码:
!function(n){function r(e){if(t[e])return t[e].exports;var o=t[e]={i:e,l:!1,exports:{}};return n[e].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var t={};r.m=n,r.c=t,r.i=function(n){return n},r.d=function(n,t,e){r.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:e})},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(t,"a",t),t},r.o=function(n,r){return Object.prototype.hasOwnProperty.call(n,r)},r.p="",r(r.s=0)}([function(n,r){function t(){noerror}t()}]);
代码如期报错,并上报相关信息:
{
msg: 'Uncaught ReferenceError: noerror is not defined',
url: 'http://127.0.0.1:8077/main.min.js',
row: '1',
col: '515'
}
可见1和515是个什么鬼?我哪知道问题出在哪?
如何定位具体错误?
我们的决绝方案是考虑不将压缩后的文件扩大/半还原成源文件的形式,去解决该问题。
SouceMap 快速定位
什么是SouceMap?
它是一个文件信息,存储着源文件的信息,以及源文件与处理后文件的映射关系。
在定位压缩代码的报错时,通过错误信息的行列数与对应的 SouceMap 文件,处理后得到源文件的具体错误信息。
SourceMap 文件中的 sourcesContent 字段对应源代码内容,不希望将 SourceMap 文件发布到外网上,而是将其存储到脚本错误处理平台上,只用在处理脚本错误中。
通过 SourceMap 文件可以得到源文件的具体错误信息,结合 sourcesContent 上源文件的内容进行可视化展示,让报错信息一目了然!
基于 SourceMap 快速定位脚本报错方案。
使用
客户端通过webpack的devtool属性,对 SouceMap 文件进行配置。
webpack.config.js 配置如下:
module.exports = {
entry: './js/main.js',
output: {},
mode: 'development',
devtool: 'eval', // 主要看这个
......
};
devtool支持的值有许多,这里介绍几个。
eval:
当值为eval时,会将每个module块执行eval,执行之后不会生成 SouceMap 文件(.map文件),仅仅是在每个模块后,增加SouceURL来关联模块处理前后的对应关系。
(function(modules) { // webpackBootstrap
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return printMe; });\n\nfunction printMe() {\n console.log('11111111');\n}\n\n//# sourceURL=webpack:///./js/demo1.js?");
/***/ "./js/main.js":
/*!********************!*\
!*** ./js/main.js ***!
\********************/
/*! no exports provided */
/***/
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ \"./js/demo1.js\");\n\n\nconsole.log('main.js');\n\n//# sourceURL=webpack:///./js/main.js?");
})
})
每一个打包后的模块后面都增加了包含sourceURL的注释,sourceURL的值是压缩前存放的代码的位置,这样就通过sourceURL关联了压缩前后的代码。
**优点:**打包速度快,原因是不需要生成 SouceMap文件。
**缺点:**映射到转换后的代码,而不是原始代码,所以无法正确显示行列数。
source-map:
source-map会为每一个打包后的module生成独立的sourcemap文件。
修改package.json文件(注意:--devtool):
scripts: {
"dev": "webpack-dev-server --progress --colors --devtool source-map --hot --inline",
}
运行npm run build,dist文件中会有.map文件。打包后的代码如下:
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ "./js/demo1.js");
__webpack_require__(/*! ../styles/main.css */ "./styles/main.css");
console.log('main.js');
/***/ }),
/***/ "./styles/main.css":
/*!*************************!*\
!*** ./styles/main.css ***!
\*************************/
/*! no static exports found */
/***/ (function(module, exports) {
// removed by extract-text-webpack-plugin
/***/ })
/******/ });
//# sourceMappingURL=bundle.js.map
如上打包后的代码最后面一句代码是 //# sourceMappingURL=bundle.js.map ,同时在dist目录下会针对每一个模块生成响应的 .map文件,
比如我们在dist目录中会生成 bundle.js.map文件,我们可以打开看下这个文件代码会如下:
{
"version":3,
"sources":[
"webpack:///webpack/bootstrap","webpack:///./js/demo1.js",
"webpack:///./js/main.js","webpack:///./styles/main.css"
],
"names":["printMe","console","log","require"],
"mappings":";AAAA;AACA;;AAEA;AACA...",
"file":"bundle.js",
"sourcesContent":[],
"sourceRoot": ""
}
属性:
version: Source Map 的版本,目前为3.
sources: 转换前的文件,该项是一个数组,表示可能存在多个文件合并.
names: 转换前的所有变量名和属性名。
mappings: 记录位置信息的字符串。
sourcesContent: 转换前的文件内容列表,与sources列表依次对应。
sourceRoot: 转换前的文件所在的目录,如果与转换前的文件在同一个目录,该项为空。
优点:
- 有具体的行列数。
缺点:
- sourcesContent会暴漏信息,不希望将 SourceMap 文件发布到外网上。
总结:
关于客户端错误监控,以及上报,就暂时总结到这里了。webpack的devtool还有许多值可以进行配置,就不一一介绍了。用哪个值取决于公司需求了。