同步宣布于 https://github.com/xianshanna…
我的发起是不要隐蔽毛病,勇敢地抛出来。没有人会由于代码涌现 bug 致使顺序崩溃而羞辱,我们能够让顺序中缀,让用户重来。毛病是没法防止的,怎样去处置惩罚它才是最主要的。
JavaScript 供应一套毛病处置惩罚机制,毛病是滋扰顺序寻常流程的非寻常的变乱。而没人能够坚持顺序没有 bug,那末上线后碰到特别的 bug,怎样更快的定位题目所在呢?这就是我们这个专题须要议论的题目。
下面会从 JavaScript Error 基础知识、怎样阻拦和捕捉非常、怎样轻易的在线上报毛病等方面来叙说,本人也是依据网上的知识点举行了一些总结和剖析(我只是互联网的搬运工,不是创造者),假如有什么讹夺的状况,请在 issue 上狠狠的指摘我。
这个专题如今是针对浏览器的,还没斟酌到 node.js,不过都是 JavaScript Es6 语法,迥然不同。
什么时刻 JavaScript 会抛出毛病呢?
寻常分为两种状况:
- JavaScript 自带毛病
- 开辟者主动抛出的毛病
JavaScript 引擎自动抛出的毛病
大多数场景下我们碰到的毛病都是这类毛病。假如发作Javscript 语法毛病、代码援用毛病、范例毛病等,JavaScript 引擎就会自动触发此类毛病。以下一些场景:
场景一
console.log(a.notExited) // 浏览器会抛出 Uncaught ReferenceError: a is not defined
场景二
const a; // 浏览器抛出 Uncaught SyntaxError: Missing initializer in const declaration
语法毛病,浏览器寻常第一时间就抛出毛病,不会比及实行的时刻才会报错。
场景三
let data; data.forEach(v=>{}) // Uncaught TypeError: Cannot read property 'forEach' of undefined
手动抛出的毛病
寻常都是类库开辟的自定义毛病非常(如参数等不合法的毛病非常抛出)。或许从新修正毛病 message 举行上报,以轻易明白。
场景一
function sum(a,b){ if(typeof a !== 'number') { throw TypeError('Expected a to be a number.'); } if(typeof b !== 'number') { throw TypeError('Expected b to be a number.'); } return a + b; } sum(3,'d'); // 浏览器抛出 uncaught TypeError: Expected b to be a number.
场景二
固然我们不一定须要如许做。
let data; try { data.forEach(v => {}); } catch (error) { error.message = 'data 没有定义,data 必需是数组。'; error.name = 'DataTypeError'; throw error; }
怎样建立 Error 对象?
建立语法以下:
new Error([message[,fileName,lineNumber]])
省略 new
语法也一样。
个中fileName
和 lineNumber
不是一切浏览器都兼容的,谷歌也不支撑,所以能够疏忽。
Error
组织函数是通用毛病范例,除了 Error
范例,另有 TypeError
、RangeError
等范例。
Error 实例
这里枚举的都是 Error 层的原型链属性和要领,更深层的原型链的继续属性和轻易不做申明。一些有兼容性的而且不经常运用的属性和要领不做申明。
console.log(Error.prototype)
// 浏览器输出 {constructor: ƒ, name: "Error", message: "", toString: ƒ}
其他毛病范例组织函数是继续 Error
,实例是一致的。
属性
- Error.prototype.message
毛病信息,
Error("msg").message === "msg"
。 - Error.prototype.name
毛病范例(名字),
Error("msg").name === "Error”
。假如是 TypeError,那末 name 为 TypeError。 - Error.prototype.stack
Error
对象作为一个非标准的栈属性供应了一种函数追踪体式格局。不管这个函数被被挪用,处于什么形式,来自于哪一行或许哪一个文件,有着什么样的参数。这个栈产生于近来一次挪用最早的那次挪用,返回原始的全局作用域挪用。这个不是范例,存在兼容性。经测试,谷歌、火狐、Edge、safar 都支撑此特征(都是在最新的版本下测试 2019-04-02),IE 不支撑。
要领
- Error.prototype.constructor
- Error.prototype.toString
返回值花样为
${name}: ${message}
。
经常运用 Error 范例
除了通用的 Error
组织函数外,JavaScript另有罕见的 5 个其他范例的毛病组织函数。
TypeError
建立一个 Error 实例,示意毛病的缘由:变量或参数不属于有效范例。
throw TypeError("范例毛病");
// Uncaught TypeError: 范例毛病
RangeError
建立一个 Error
实例,示意毛病的缘由:数值变量或参数超越其有效局限。
throw RangeError("数值超越有效局限");
// Uncaught RangeError: 数值超越有效局限
ReferenceError
建立一个 Error
实例,示意毛病的缘由:无效援用。
throw ReferenceError("无效援用");
// Uncaught ReferenceError: 无效援用
SyntaxError
建立一个 Error
实例,示意毛病的缘由:语法毛病。这类场景很罕用,除非类库定义了新语法(如模板语法)。
throw SyntaxError("语法毛病");
// Uncaught SyntaxError: 语法毛病
URIError
建立一个 Error
实例,示意毛病的缘由:涉及到 uri 相干的毛病。
throw URIError("url 不合法");
// Uncaught RangeError: url 不合法
自定义 Error 范例
自定义新的 Error 范例须要继续 Error ,以下自定义 CustomError
:
function CustomError(...args){
class InnerCustomError extends Error {
name = "CustomError";
}
return new InnerCustomError(...args);
}
继续 Error 后,我们只须要对 name
做重写,然后封装成可直接挪用的函数即可。
怎样阻拦 JavaScript 毛病?
既然没人能保证 web 运用不会涌现 bug,那末涌现非常报错时,怎样阻拦并举行一些操纵呢?
try…catch… 阻拦
这是阻拦 JavaScript 毛病,阻拦后,假如不手动抛出毛病,这个毛病将寂静处置惩罚。寻常写代码假如我们晓得某段代码可能会涌现报错题目,就能够运用这类体式格局。以下:
const { data } = this.props;
try {
data.forEach(d=>{});
// 假如 data 不是数组就会报错
} catch(err){
console.error(err);
// 这里能够做上报处置惩罚等操纵
}
一些运用体式格局
非常不友爱的处置惩罚体式格局
try...catch...
运用须要注重,try…catch… 后,毛病会被阻拦,假如不主动抛出毛病,那末没法晓得报错位置。以下面的处置惩罚体式格局就是不好的。
function badHandler(fn) {
try {
return fn();
} catch (err) { /**noop,不做任何处置惩罚**/ }
return null;
}
badHandler();
如许 fn 回调发送毛病后,我们没法晓得毛病是在那里发作的,由于已被 try…catch 了,那末怎样处理这个题目呢?
相对友爱但蹩脚的处置惩罚体式格局
function CustomError(...args){
class InnerCustomError extends Error {
name = "CustomError";
}
return new InnerCustomError(...args);
}
function uglyHandlerImproved(fn) {
try {
return fn();
} catch (err) {
throw new CustomError(err.message);
}
return null;
}
badHandler();
如今,这个自定义的毛病对象包括了底本毛病的信息,因而变得越发有效。然则由于再度抛出来,依然是未处置惩罚的毛病。
try…catch… 能够阻拦异步毛病吗?
这个也要分场景,也看个人的明白方向,起首明白下面这句话:
try…catch 只会阻拦当前实行环境的毛病,try 块中的异步已离开了当前的实行环境,所以 try…catch… 无效。
setTimeout
和 Promise
都没法经由过程 try…catch 捕捉到毛病,指的是 try 包括异步(非当前实行环境),不是异步包括 try(当前实行环境)。异步无效和有效 try…catch 以下:
setTimeout
这个无效:
try { setTimeout(() => { data.forEach(d => {}); }); } catch (err) { console.log('这里不会运转'); }
下面的 try…catch 才会有效:
setTimeout(() => { try { data.forEach(d => {}); } catch (err) { console.log('这里会运转'); } });
Promise
这个无效:
try { new Promise(resolve => { data.forEach(d => {}); resolve(); }); } catch (err) { console.log('这里不会运转'); }
下面的 try…catch 才会有效:
new Promise(resolve => { try { data.forEach(d => {}); } catch (err) { console.log('这里会运转'); } });
小结
不是一切场景都须要 try…catch… 的,假如一切须要的处所都 try…catch,那末代码将变得痴肥,可读性变差,开辟效力变低。那末我须要一致猎取毛病信息呢?有无更好的处置惩罚体式格局?固然有,后续会提到。
Promise 毛病阻拦
Promise.prototype.catch
能够到达 try…catch 一样的结果,只如果在 Promise 相干的处置惩罚中报错,都会被 catch 到。固然假如你在相干回调函数中 try…catch,然后做了寂静提醒,那末也是 catch 不到的。
以下会被 catch 到:
new Promise(resolve => {
data.forEach(v => {});
}).catch(err=>{/*这里会运转*/})
下面的不会被 catch 到:
new Promise(resolve => {
try {
data.forEach(v => {});
}catch(err){}
}).catch(err=>{/*这里不会运转*/})
Promise 毛病阻拦,这里就不细致说了,假如你看懂了 try…catch,这个也很好明白。
setTimeout 等其他异步毛病阻拦呢?
如今没有相干的体式格局直接阻拦 setTimeout 等其他异步操纵。
假如要阻拦 setTimeout 等异步毛病,我们须要在异步回调代码中处置惩罚,如:
setTimeout(() => {
try {
data.forEach(d => {});
} catch (err) {
console.log('这里会运转');
}
});
如许能够阻拦到 setTimeout 回调发作的毛病,然则假如是下面如许 try…catch 是无效的:
try {
setTimeout(() => {
data.forEach(d => {});
});
} catch (err) {
console.log('这里不会运转');
}
怎样猎取 JavaScript 毛病信息?
你能够运用上面阻拦毛病信息的体式格局猎取到毛病信息。然则呢,你要每一个场景都要去阻拦一遍吗?起首我们不确定什么处所会发作毛病,然后我们也不可能每一个处所都去阻拦毛病。
不必忧郁,JavaScript 也斟酌到了这一点,供应了一些便利的猎取体式格局(不是阻拦,毛病照样会停止顺序的运转,除非主动阻拦了)。
window.onerror 事宜猎取毛病信息
onerror 事宜不管是异步照样非异步毛病(除了 Promise 毛病),onerror 都能捕捉到运转时毛病。
须要注重一下几点:
- window.onerror 函数只要在返回 true 的时刻,非常才不会向上抛出,不然即使是晓得非常的发作控制台照样会显现 Uncaught Error: xxxxx。假如运用
addEventListener
,event.preventDefault()
能够到达一样的结果。 - window.onerror 是没法捕捉到收集非常的毛病、
<img/>
或<script>
资本加载失利等毛病。不过假如你运用了 fetch 等支撑 promise 的体式格局,毛病能够经由过程 unhandledrejection 体式格局拿到毛病信息。
语法:
window.onerror
window.onerror = function(message, source, lineno, colno, error) { ... }
window.addEventListener(‘error’)
window.addEventListener('error', function(event) { ... })
细致看这里。
unhandledrejection 事宜猎取 Promise 毛病
unhandledrejection 存在兼容性题目,IE、Edge、火狐等如今都不支撑。
用法以下:
window.addEventListener("unhandledrejection", event => {
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
或许
window.onunhandledrejection = event => {
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
};
细致看这里。
怎样处置惩罚毛病信息?
毛病对象 Error
包括以下的字段返回:
- name
毛病的范例,寻常有
Error
、TypeError
、ReferenceError
、RangeError
、URIError
等,固然也能够是自定义的毛病范例。 - message
毛病的细致信息,能够是 JavaScript 自动抛出的毛病信息,也能够是手动抛出的自定义信息。
- stack
这个不是范例,存在兼容性。经测试,谷歌、火狐、Edge、safar 都支撑此特征(都是在最新的版本下测试 2019-04-02),IE 不支撑。
name
和 message
我们都不必做什么处置惩罚,主如果要针对 stack 做处置惩罚,寻常我们须要把,这三个字段的信息提交到毛病处置惩罚系统,针对性处置惩罚。
同时我们天生环境的代码是被紧缩后的代码,须要运用 sourceMap 举行映照复原代码。
后续会补充这个议论。