一文弄懂JavaScript的错误类型,捕获及上报
1.错误类型和对象
- 语法错误:代码中存在拼写错误,将导致程序完全或部分不能运行
- 逻辑错误:代码语法正确,但执行结果不匹配预期
1.Error
- 基础的错误对象,其他错误对象均继承它

<body>
<button type="button" id="btnEx">执行</button>
<script>
function trace() {
try {
throw new Error("哦豁,错误哦");
} catch (err) {
console.log("err.name", err.name);
console.log("err.message", err.message);
console.log("err.stack", err.stack);
console.log("err.constructor:", err.constructor);
}
}
function b() {
trace();
}
btnEx.onclick = b;
</script>
</body>
执行结果如下:
2.EvalError
- 历史遗孤,eval相关的错误
- 产生:
- 不是被直接调用或者被赋值
- 理论上现代浏览器不会再抛出此错误
3.InternalError
- 红宝书和MDN都有提到这个对象,可惜的是只有
firefox
支持 - 产生:
- 过多
case
语句 - 正则表达式中括号过多
- 递归过深等
- 过多
4.RangeError
- 当一个值不在其允许的范围或者集合中
- 当传递一个不合法的
length
值作为Array
构造器的参数创建数组 - 传递错误值到数值计算方法(
Number.toExponential()
,Number.toFixed()
,Number.toPrecision()
等)
5.ReferenceError
- 一个不存在的变量被引用时发生的错误
6.SyntaxError
- 解析语法上不合法的代码的错误
- 不能被用户代码
catch
的SyntaXError
btn.onclick = function () {
try {
const hhhh bbbb;
} catch (e) {
console.log('e.message:', e.message);
}
}
- 可以被用户代码捕获的
btn.onclick = function () {
try {
new Function(`const hhhh bbbb`);
} catch (e) {
console.log("e.message:", e.message);
}
};
7.TypeError
- 值的类型非预期类型时发生的错误
undefined.abc
new 666()
"hello" in "hello yun"
const a = 1;
a = 2
8.URIError
- 使用全局
URI
处理函数而产生的错误 encodeURL
、encodeURIComponent
、decodeURI
、decodeURIComponent
方法会产生URIError
错误
9.AggregateError
- 包含多个错误信息的错误
Promise.any([
Promise.reject(new Error("error 1")),
Promise.reject(new Error("error 2")),
]).catch(e => {
console.log("e instanceof AggregateError:", e instanceof AggregateError);
console.log("e.message:", e.message);
console.log("e.name", e.name);
console.log("e.errors", e.errors);
});
打印结果如下:
2.异常类型判断和自定义异常
throw
- 在
JavaScript
中,throw
关键字可以将错误抛出 - 但是
throw
不仅仅只能抛出错误对象,还可以抛出基本类型数据
try {
throw "错误字符串";
} catch (e) {
console.log(typeof e, "==name==", e.name, "===", e);
}
try {
throw 666;
} catch (e) {
console.log(typeof e, "==name==", e.name, "===", e);
}
function UserException(message) {
this.message = message;
this.name = "UserException";
}
try {
// 抛出对象
throw new UserException("无效异常");
} catch (e) {
console.log(typeof e, "==name==", e.name, "===", e);
}
// 尽量抛出内置的Error
function throwError(a) {
if (a > 10) {
throw new RangeError("值超出限制");
}
}
try {
throwError(15);
} catch (e) {
console.log(e.name, "==e.message==", e.message);
}
// 异常无法处理,重新抛出
try {
throw 500;
} catch (e) {
if (e <= 50) {
console.log("已处理");
} else {
// 异常无法处理,重新抛出
throw e;
}
}
自定义异常类型
ES6实现
class CustomError extends Error {
constructor(foo = "bar", ...params) {
super(...params);
// 不是必需
// if (Error.captureStackTrace) {
// Error.captureStackTrace(this, CustomError);
// }
this.name = "CustomError";
this.foo = foo;
this.date = new Date();
}
}
function trace() {
try {
throw new CustomError("baz", "bazMessage");
} catch (e) {
console.log("是否是MyError类型错误:", e instanceof CustomError);
console.log("e.message:", e.message);
console.log("e.name", e.name);
console.log("e.foo", e.foo);
console.log("e.date", e.date);
console.log("e.stack:", e.stack);
}
}
function b() {
trace();
}
function a() {
b();
}
a();
ES5实现
function MyError(message) {
this.name = "MyError";
this.message = message || "Default Message";
// this.stack = (new Error()).stack; 和下面方法类似,也是拿到对应调用的堆栈信息
/*
* Error.captureStackTrace(targetObject[, constructorOpt])
* 参数 targetObject -> 表示一个对象
* 参数 constructorOpt -> 表示对象的构造函数
* 在targetObject上创建一个.stack属性, 调用是返回一个调用 Error.captureStackTrace() 的位置的字符串。
*/
Error.captureStackTrace(this, MyError);
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
function trace() {
try {
throw new MyError("custom message");
} catch (e) {
console.log("是否是MyError类型错误:", e instanceof MyError); // true
console.log("e.message:", e.message); // 'custom message'
console.log("stack:", e.stack);
}
}
function b() {
trace();
}
function a() {
b();
}
a();
异常类型的判断
1.instanceof
function testReferenceError() {
try {
let a = undefinedVariable;
} catch (e) {
console.log("instanceof ReferenceError :", e instanceof ReferenceError); // true
}
}
testReferenceError();
2.constructor
function testTypeError() {
// TypeError类型错误
try {
new 666();
} catch (err) {
console.log("constructor TypeError", err.constructor === TypeError); // constructor TypeError true
}
}
function testError() {
//Error
try {
throw new Error("哦豁,错误哦");
} catch (err) {
console.log("constructor Error", err.constructor === Error); // constructor Error true
}
}
testTypeError();
testError();
3.error.name
function testURIError() {
try {
decodeURIComponent('%')
} catch (e) {
console.log("ErrorName:",e.name === "URIError")
}
}
testURIError(); // ErrorName: true
注意AggregateError
如果使用instanceof
判断会出错,此时可借助name
属性
function print(e) {
console.log("错误类型:", e.constructor.name);
console.log("错误信息:", e.message);
if (e.constructor.name == "AggregateError" && e.errors) {
for (let i = 0; i < e.errors.length; i++) {
console.log(`错误信息${i}:`, e.errors[i].message);
}
}
}
function testError() {
//AggregateError
Promise.any([
Promise.reject(new Error("error 1")),
Promise.reject(new Error("error 2")),
]).catch((e) => {
print(e);
});
}
testError();
打印结果如下:
3.异常捕获
1.try catch
const str = "yunmu";
try {
JSON.parse(str);
} catch (e) {
console.log("解析字符串错误");
} finally {
console.log("finally处理");
}
try {
const a = {};
a.b();
} catch (e) {
console.log("捕捉到错误", e.message);
}
有些错误无法捕获
//编译时错误,还没有执行到try catch
try{
dd.
}catch(e){
console.log("捕捉到错误",e.message);
}
//try catch已经执行完毕,无法捕获,可在异步操作里面进行try catch捕获
try{
setTimeout(()=>{
const a = {};
a.b();
})
}catch(e){
console.log("捕捉到错误",e.message);
}
2.window.onerror(捕获全局JS异常)
window.onerror = function (message, url, line, column, error) {
console.log(
"捕获到错误:",
message,
"==line:",
line,
"==column:",
column,
"==error:",
error
);
};
setTimeout(() => {
a.b();
});
const f = {};
f.cc.ee;
执行结果如下:
引入跨域JS文件获取具体错误信息处理
script
引用JS
文件时增加crossorigin="anonymous"
的属性- 如果是动态加载的JS,可以写
script.crossOrigin = true
- 并且要为
JS
资源文件增加CORS
响应头(服务),一般的CDN
网站都会将Access-Control-Allow-Origin
配置为*
3. window.addEventListener("error")
window.onerror
与window.addEventListener("error")
都可以捕获js错误- 但是使用
addEventListener
可以捕获静态资源加载错误,但必须要设置捕获阶段,即第三个参数为true
- 捕获到静态资源错误,无法区分
404
或者500
,需结合服务端
<body>
<div>script error 解决</div>
<script>
// 捕获不到静态资源加载错误
window.onerror = function (message, url, line, column, error) {
console.log(
"window.onerror 捕获到错误:",
message,
"==line:",
line,
"==column:",
column,
"==error:",
error
);
};
window.addEventListener(
"error",
function (event) {
console.log(
"window.addEventListener:",
event,
"==e.error:",
event.error
);
if (event.target && (event.target.src || event.target.href)) {
//静态资源
console.log({
type: "error", //resource
errorType: "resourceError",
filename: event.target.src || event.target.href, //加载失败的资源
tagName: event.target.tagName, //标签名
});
}
},
true
); ////由于网络请求不会冒泡,必须在捕获阶段处理
</script>
<img src="http://127.0.0.1:3000/test.png" />
<script src="http://127.0.0.1:3000/test2.js" />
</body>
4.unhandledrejection,rejectionhandled
unhandledrejection
:当Promise
被reject
且没有reject
处理器的时候,会触发unhandledrejection
事件
window.addEventListener("unhandledrejection", function (e) {
//阻断异常继续抛出
e.preventDefault();
console.log("unhandledrejection捕获到promise错误的原因是", e.reason);
console.log("Promise 对象是", e.promise);
return true;
});
new Promise((resolve, reject) => {
reject("promise error1");
});
new Promise((resolve) => {
resolve();
}).then(() => {
throw new Error("promise error2");
});
try {
new Promise((resolve) => {
resolve();
}).then(() => {
throw new Error("promise error3");
});
} catch (e) {
console.log("try catch:", e);
}
打印结果如下:
rejectionhandled
:当Promise
被rejected
但rejection
处理器没有及时处理时会在全局触发rejectionhandled
事件
window.addEventListener("unhandledrejection", function (e) {
//阻断异常继续抛出
e.preventDefault();
console.log("unhandledrejection捕获到promise错误的原因是:", e.reason);
console.log("unhandledrejection Promise 对象是:", e.promise);
return true;
});
//promise异常被处理了
window.addEventListener("rejectionhandled", (e) => {
// rejected的原因
console.log("rejectionhandled:", e.reason);
});
const p1 = new Promise((resolve, reject) => {
reject("promise error1");
});
setTimeout(() => {
p1.catch((e) => {
console.log("catch捕获到promise1 错误:", e);
});
}, 1000);
打印结果如下:
5.XMLHttpRequest、fetch、axios-网络请求
XMLHttpRequest
:自己有onerror
事件,可以自己封装一个库fetch
:自带catch
axios
:也有完整的错误处理机制
6.ErrorBoundary-react异常
- 子组件的渲染
- 生命周期函数
- 构造函数
7.errorHandler-vue异常
vue.config.errorHandler = (err, vm, info) {}
4.异常上报
1.发请求上报
axios.post(url, data);
但因为请求异步可被cancel
,建议使用XMLHttpRequest
发送同步请求
const syncReport = (url, { data = {}, headers = {} } = {}) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, false);
xhr.withCredentials = true;
Object.keys(headers).forEach((key) => {
xhr.setRequestHeader(key, headers[key]);
});
xhr.send(JSON.stringify(data));
};
2.sendBeacon
- 数据发送可靠
- 数据异步传输
- 不影响下一个导航的载入
// 请求数据string,自动设置Content-Type 为 text/plain
const reportData = (url, data) => {
navigator.sendBeacon(url, data);
};
// Blob一般手动设置其MIME type,一般设置为 application/x-www-form-urlencoded
const reportData = (url, data) => {
const blob = new Blob([JSON.stringify(data), {
type: 'application/x-www-form-urlencoded',
}]);
navigator.sendBeacon(url, blob);
};
// 可以直接创建一个新的Formdata,请求头自动设置Content-Type为multipart/form-data
const reportData = (url, data) => {
const formData = new FormData();
Object.keys(data).forEach((key) => {
let value = data[key];
if (typeof value !== 'string') {
// formData只能append string 或 Blob
value = JSON.stringify(value);
}
formData.append(key, value);
});
navigator.sendBeacon(url, formData);
};
我推荐使用 sendBeacon
发送数据,如不支持,则可以降级使用同步的 ajax
发送数据
3.gif图片
- 图片
src
属性可以直接跨域访问 - 相比
PNG/JPG
,gif
的体积可以达到最小,合法的GIF只需要43
个字节 - 一般采用
1*1
像素透明色来上报,不存储色彩空间数据,节约化
const reportData = (url, data) => {
let img = document.createElement('img');
const params = [];
Object.keys(data).forEach((key) => {
params.push(`${key}=${encodeURIComponent(data[key])}`);
});
img.onload = () => img = null;
img.src = `${url}?${params.join('&')}`;
};
上报时机
可在页面加载卸载,tab、路由切换等时机都可上报
// 页面加载
window.addEventListener('load', reportData, false);
// 页面卸载
window.addEventListener('beforeunload', reportData, false);
// tab切换
document.addEventListener("visibilitychange", function() {
if(document.visibilityState === 'visible') {
reportData1();
}
if(document.visibilityState === 'hidden') {
reportData2();
}.
});
转载自:https://juejin.cn/post/7159231003712552973