一、前言
前端错误类型:
-
同步JS 语法报错,如变量未定义 - VM212:1 Uncaught ReferenceError: xx is not defined
-
Promise异常,如Promise.reject(xx) 未捕获的异常 - Uncaught (in promise) xx
-
异步代码执行过程中产生的错误, 如 setTimeout, promise.then(), try.catch 无法捕获异步
-
html资源解析异常,如img, script 引入静态资源出错的错误,不会触发冒泡,只能触发onerror的捕获
-
接口响应异常,通过状态码拦截, 请求方法(原生XMLHttpRequest / axios / fetch)
二、 GlobalEventHandlers.onerror
该方法用来解决同步JS 语法报错跟 html 资源解析异常
介绍
Error事件的事件处理程序, 在各种目标对象的不同类型错误被触发:
- 当JavaScript运行时错误(包括语法错误)发生时,window会触发一个ErrorEvent接口的error事件,并执行window.onerror()。
- 同步语法错误通过try.catch进行拦截 / 异步错误 try.catch 拦截不到, 需要 onerror捕获, 但是还是会有错误打印
- 当一项资源(如
或
语法
由于历史原因,window.onerror
和element.onerror
接受不同的参数。
1、window.onerror
window.onerror = function(message, source, lineno, colno, error) { ... }
- message:错误信息(字符串),可用于HTML onerror=""处理程序中的event。
- source:发生错误的脚本URL(字符串)
- lineno:发生错误的行号(数字)
- colno:发生错误的列号(数字)
- error:Error对象(对象)若该函数返回true,则阻止执行默认事件处理函数。
window.addEventListener('error', function(event) { ... })
2. element.onerror
element.onerror = function(event) { ... }
element.onerror使用单一Event参数的函数作为其处理函数。
三、 try...catch...
try...catch语句标记要尝试的语句块,并指定一个出现异常时抛出的响应。可以捕获同步异常
try块的代码可能会抛出三种异常:TypeError,RangeError 和 EvalError
语法
try {
try_statements -- 执行的代码块
}
[catch (exception_var_1 if condition_1) { // 可以添加条件catch
catch_statements_1
}]
...
[catch (exception_var_2) {
catch_statements_2
}]
[finally {
finally_statements
}]
四、 addEventListener('unhandledrejection', e => { ...} )
当 Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件;
这可能发生在 window 下,但也可能发生在 Worker 中。 这对于调试回退错误处理非常有用。
unhandledrejection 继承自 PromiseRejectionEvent,而 PromiseRejectionEvent 又继承自 Event。
因此unhandledrejection 含有 PromiseRejectionEvent 和 Event 的属性和方法。
1. 基础上报语法
window.addEventListener("unhandledrejection", event => {
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
或者
window.onunhandledrejection = event => {
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
};
2. 阻止默认事件写法 (调用 preventDefault()
来取消该事件,从而防止该事件冒泡并由运行时的日志代码处理)
window.addEventListener('unhandledrejection', function (event) {
// ...您的代码可以处理未处理的拒绝...
// 防止默认处理(例如将错误输出到控制台)
event.preventDefault();
});
五、 XMLHttpRequest() / axios / fetch 监控拦截状态码
1. XMLHttpRequest
class Request {
constructor({ headers, timeout, success, fail }) {
this.ajax = new XMLHttpRequest();
this.headers = headers;
this.timeout = timeout;
this.success = success;
this.fail = fail;
this.url = "";
this.params = {};
this.init();
}
init() {
// 2. 拦截状态码, 进行上报
this.ajax.onreadystatechange = function () {
const { readyState, status, responseText } = this;
if (readyState === 4) {
if (status === 200) {
// 调用成功回调
this.success(responseText);
} else {
// 调用失败回调
this.fail({ status, responseText });
this.report({ status, responseText });
}
}
};
// 3. 异常拦截
this.ajax.onerror = function (e) {
this.report({ status, responseText });
};
}
setHeaders() {
Object.keys(this.headers).forEach((key) => {
this.ajax.setRequestHeader(ket, this.headers[key]);
});
}
report({ status, responseText }) {
// ... 上报异常
}
get({ url, params }) {
// url拼接
this.url = url;
this.params = params;
this.paramsJoinUrl(url, params);
this.ajax.open("GET", url, true);
// 设置请求头及超时时间
this.ajax.setRequestHeader("Content-Type", "application/json"); this.commonSet();
this.ajax.send(null);
}
commonSet() {
// 设置公共请求头
this.setHeaders();
// 设置超时时间
this.ajax.timeout = 2000; // 超时时间,单位是毫秒
}
post({ url, data }) {
this.ajax.open("POST", url, true);
// 设置请求头及超时时间
this.ajax.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded"
);
this.commonSet();
this.ajax.send({ data });
}
abort() {
// 终止该请求。当一个请求被终止,它的 readyState 将被置为 XMLHttpRequest.UNSENT (0),并且请求的 status 置为 0。
this.ajax.abort();
}
paramsJoinUrl() {
const hasQuestioin = this.url.includes("?") >= 0;
const arr = [];
Object.keys(this.params).forEach((key) => {
arr.push(`${key}=${this.params[key]}`);
});
const path = arr.join("&");
const search = hasQuestioin ? `&${path}` : path;
this.url += search;
}
}
2. axios 通过请求响应拦截
const instance = axios.create({
baseURL: "https://api.example.com",
});
// 添加请求拦截器
axios.interceptors.request.use(
(config) => {
return config;
},
(err) => {
return Promise.reject(err);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
(res) => {
const { status, data } = res;
if (status >= 200 && status < 300) {
return data;
} else {
// 进行上报
report({ err: res });
}
return res;
},
(err) => {
// 进行上报
report({ err });
return Promise.reject(err);
}
);
3. fetch
class Request {
request(url = "", method, data = {}) {
return new Promise((resolve, reject) => {
fetch(url, {
method, // *GET, POST, PUT, DELETE, etc.
mode: "cors", // no-cors, *cors, same-origin
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "same-origin", // include, *same-origin, omit
headers: {
"Content-Type": "application/json",
// 'Content-Type': 'application/x-www-form-urlencoded',
},
body: JSON.stringify(data), // body data type must match "Content-Type" header
})
.then((res) => {
const { status, ok } = res;
if (ok) {
resolve(res.json());
} else {
reject(res);
// 进行上报
report({ err: res });
}
})
.catch((err) => {
reject(err);
// 进行上报
report({ err: res });
});
});
}
}
示例:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>我理解的前端 error 监控</title> </head> <body> <!-- 1. img异常 进 onerror 的捕获 --> <img src="hlll" alt="111" /> <script> // 2. Promise.reject 进入 unhandledrejection, 结果值是 event.reason Promise.reject(11111) // 3. 外部同步异常代码进入 onerror 冒泡 window.addEventListener('error', e => { console.log('onerror 冒泡', e) }) // img加载等资源有问题 window.addEventListener( 'error', e => { console.log('onerror 捕获', e) }, true ) // promise.reject window.addEventListener( 'unhandledrejection', e => { console.log('reject unhandledrejection', e) }, true ) try { setTimeout(() => { console.log1(222) }, 10) console.log(aaa) } catch (e) { console.log('try catch', e) } console.log(bbb) </script> </body></html>
总结:
-
window.onerror 冒泡方案 用来解决 同步代码异常、setTimeout 异常,window.onerror 捕获方案 用来解决 html 资源加载失败问题
-
try catch 内部同步代码的异常通过 try...catch...捕获
-
Promise.reject() 通过 unhandledrejection 捕获
-
接口返回状态通过判断状态码上报埋点捕获
文章链接:
developer.mozilla.org/en-US/docs/…
developer.mozilla.org/en-US/docs/…