如何及时发现网页的隐形错误
在上一篇文章前端监控究竟有多重要?大家了解了前端监控系统的重要性以及前端监控的组成部分、常见的监控指标、埋点方式。
接下来这篇文章我们就来详细学习一下前端监控系统中的,异常监控。
想要进行异常监控之前,肯定先要了解有哪些异常才能进行监控。
异常的类型
一般来说,浏览器端的异常分为两种类型:
- JavaScript 错误,一般都是来自代码的原因。
- 静态资源错误,一般都是来着资源加载的原因
而这里面我们又有各自的差异
JavaScript 错误
先来说说JavaScript的错误类型,ECMA-262 定义了 7 种错误类型,说明如下:
EvalError
:eval() 函数的相关的错误RangeError
:使用了超出了 JavaScript 的限制或范围的值。ReferenceError
: 引用了未定义的变量或对象TypeError
: 类型错误URIError
: URI操作错误SyntaxError
: 语法错误 (这个错误WebIDL中故意省略,保留给ES解析器使用)Error
: 普通异常,通常与 throw 语句和 try/catch 语句一起使用,利用属性 name 可以声明或了解异常的类型,利用message 属性可以设置和读取异常的详细信息。
如果想更详细了解可以看详细错误罗列这篇文章
静态资源错误
- 通过 XMLHttpRequest、Fetch() 的方式来请求的 http 资源时。
- 利用
、
等标签加载的资源。 - 通过创建实例的方式,例如
new Image()
等代码来实现初始化。
那既然我们已经知道了我们的网站在客户端运行时可能会出现这些异常。那我们要如何进行捕获错误信息呢?
捕捉错误
我们常见的几种捕捉方法有
try-catch
(ES提供基本的错误捕获语法)window.onerror
= cb (DOM0)window.addEventListener('error', cb, true)
(DOM2)window.addEventListener("unhandledrejection", cb)
(DOM4)Promise.then().catch(cb)
封装XMLHttpRequest&fetch
| 覆写请求接口对象
可能我们之前只用过try-catch
这种方法,其他的之前都没见过,没关系下面我们来逐个讲解。
try-catch
try-catch 我们经常能看见,通过给代码块进行 try-catch 进行包装后,当代码块发生出错时 catch 将能捕捉到错误的信息,页面也将可以继续执行。
优点:
- 简单易用,可以捕获同步代码的错误。
- 可以返回较完整的错误堆栈信息。
缺点:
- 缺点是无法捕获异步异常
- 无法捕捉跨域异常
- 需要手动放置,代码冗余
捕捉同步代码
// 同步异常的捕获
function foo () {
doSomething()
}
try {
foo()
} catch (e) {
console.log(e)
// 可以打印出完整的错误堆栈信息
}
无法捕捉异步代码示例
// 异步异常的捕获
function asyncFunc() {
setTimeout(() => {
throw new Error("这是一个异步代码中的错误");
}, 1000);
}
try {
asyncFunc();
} catch (e) {
// 获取不到任何信息!
}
window.onerror
优点:
- 可以捕获同步和异步的异常
- 可以获取到错误的详细信息
缺点:
- 受到同源策略的限制,只能捕获当前域名下的错误
- 无法捕获语法错误和网络异常的错误
- 无法阻止异常继续传播,捕获到错误后,无法处理异常。
代码示例:
window.onerror = function(message, source, line, column, error) {
// 输出错误信息
console.log("错误信息:" + message);
console.log("错误来源:" + source);
console.log("错误行号:" + line);
console.log("错误列号:" + column);
// 如果有错误对象,输出错误堆栈
if (error) {
console.log("错误堆栈:" + error.stack);
}
};
注意:
- window.onerror可以帮助我们捕获意料之外的错误,而 try-catch 则是用于在可预见的情况下监控特定的错误。将它们结合使用可以使错误处理更加高效。
- 只有当 window.onerror 函数返回 true 时,异常才不会继续向上抛出。否则,即使我们知道异常发生了,控制台仍然会显示 "Uncaught Error: xxxxx"。
- 对于全局捕获事件 window.onerror,最好将其放置在所有 JavaScript 脚本之前。因为无法保证我们编写的代码是否会出错,如果将其放置在后面,一旦发生错误,onerror 将无法捕获到异常。
- window.onerror 只能同时订阅一个错误处理函数。如果在代码中多次设置 window.onerror,后面的设置会覆盖前面的设置。
window.addEventListener(error、unhandledrejection)
优点:
- 可以捕获全局范围内发生的未处理异常,无论是同步还是异步代码
- 错误信息详细
- 可以自定义错误处理
缺点:
- 无法图片加载错误、资源加载错误
- 无法阻止错误冒泡
- 无法提供错误堆栈
//监听错误
window.addEventListener('error', function(event) {
// 输出错误信息
console.log("错误信息:" + event.message);
console.log("错误来源:" + event.filename);
console.log("错误行号:" + event.lineno);
console.log("错误列号:" + event.colno);
// 如果有错误对象,输出错误堆栈
if (event.error) {
console.log("错误堆栈:" + event.error.stack);
}
}, true);
//监听微任务Promise异常
window.addEventListener('unhandledrejection', function(event) {
// 输出错误信息
console.log("错误信息:" + event.message);
console.log("错误来源:" + event.filename);
console.log("错误行号:" + event.lineno);
console.log("错误列号:" + event.colno);
// 如果有错误对象,输出错误堆栈
if (event.error) {
console.log("错误堆栈:" + event.error.stack);
}
}, true);
Promise.then().catch(cb)
优点:
- 可以捕获 Promise 的拒绝(失败)状态,并执行相应的错误处理逻辑
- 可以很方便地处理 Promise 的成功和失败回调
缺点:
- 无法捕获 Promise 内部的同步异常,只能捕获到 Promise 对象本身的异常
- 无法捕获到其他异步操作中的错误,例如网络请求失败等。
function fetchUserData() {
return new Promise((resolve, reject) => {
// 模拟异步请求
setTimeout(() => {
const random = Math.random();
if (random < 0.5) {
resolve({ name: "Alice", age: 25 });
} else {
reject(new Error("请求失败"));
}
}, 1000);
});
}
fetchUserData()
.then((data) => {
console.log("用户信息:", data);
})
.catch((error) => {
console.log("请求失败:", error.message);
});
// 示例:同步代码中的错误
try {
throw new Error("这是一个同步代码中的错误");
} catch (e) {
console.log(e);
}
自己封装XMLHttpRequest&fetch `| 覆写请求接口对象
优点:
- 可以更灵活地控制请求的细节,例如设置请求头、发送 FormData 数据等。
- 可以捕捉请求过程中的各个阶段的错误,如请求失败、超时等
缺点:
- 需要编写更多的代码来处理请求细节,容易出现回调地狱。
- 需要手动处理跨域问题
- 不支持 Promise,需要使用回调函数来处理响应结果。
// 覆写XMLHttpRequest API
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) {
report(event)
}
}
xmlhttp.prototype.send = function () {
if (this['addEventListener']) {
this['addEventListener']('error', _handleEvent);
this['addEventListener']('load', _handleEvent);
this['addEventListener']('abort', _handleEvent);
this['addEventListener']('close', _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);
}
其他的一些捕获异常方式
- Vue提供的错误处理回调——Vue.errorHandler(针对与vue框架,无法捕获异步异常)
- 微信小程序提供的错误处理——onError(异步、同步都可以捕获)。
业界已经有较好的监控系统
- Sentry开源
- Webfunny
- fundebug
- 阿里的ARMS
- FrontJS
作者:zayyo
来源:juejin.cn/post/7281436708411342909
来源:juejin.cn/post/7281436708411342909