异常分类
- 页面js报错
- ajax请求异常
- 页面资源加载异常(如:如图片或脚本)
- promise异常
- iframe加载异常
- 页面崩溃和卡顿异常
常见的try catch
不能捕获异步的错误。比如这个setTimeout内部的错误捕获不到
try-catch 是用来在可预见情况下监控特定的错误
try {
setTimeout(() => {
logErrorInfo();// 上报错误
}, 1000);
} catch (e) {}
处理页面js报错
window.onerror = function(message,source,lineno,colno,error){
message:出错误的信息
source:出错误的文件
lineno:出错的代码行号
colno:出错的代码列号
error:错误的对象,包含错误的具体信息
}
window.onerror的捕获特点是:
- 只能捕获到运行时错误,无法捕获语法错误,语法错误就根本通不过JS的代码分析,也不可能运行。
- 可以捕获同步代码和异步代码的异常。
- 主要是来捕获预料之外的错误。
- 捕获不到静态资源加载异常。
- 最好写在所有 JS 脚本的前面,否则有可能捕获不到错误;
补充一点:window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生控制台还是会显示 Uncaught Error: xxxxx
处理页面资源加载异常
当一项资源加载失败,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的onerror() 处理函数。
这些 error 事件不会向上冒泡到 window ,不过能被单一的window.addEventListener 捕获。
window.addEventListener('error',function(error){
logErrorInfo()
// 要设置允许冒泡
},true)
由于网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行,但是这种方式虽然可以捕捉到网络请求的异常,但是无法判断 HTTP 的状态是 404 还是其他比如 500 等等,所以还需要配合服务端日志才进行排查分析才可以。
window.addEventListener('error',()=>{})跟window.onerror的区别是:
- window.onerror是标准错误捕获接口,window.addEventListener('error')不那么标准,不同浏览器下返回的 error 对象可能不同,需要注意兼容处理。
- window.onError虽然标准但是不能捕获资源加载失败,window.addEventListener('error')就可以。
- 需要注意避免window.addEventListener重复监听。window.onerror不存在重复监听的现象。
promise异常
- 在promise 中使用 catch 可以非常方便的捕获到异步 error。
- 没有写 catch 的 Promise 中抛出的错误无法被 onerror 或 try-catch 捕获到。
- 解决方案:为了防止有漏掉的 Promise 异常,建议在全局增加一个对 unhandledrejection 的监听,用来全局监听Uncaught Promise Error
- 补充一点:如果去掉控制台的异常显示,需要加上:event.preventDefault();
window.addEventListener('unhandledrejection',function(error){
logErrorInfo()
// event.preventDefault();
})
处理vue异常 -> errHandler
Vue.config.errHandler = (err,vm,info){
logErrorInfo()
}
处理react异常 -> componentDidCatch
class ErrorBoundat extends React.Compoent{
componentDidCatch(error , info) {
logErrorInfo()
}
}
处理iframe加载异常
window.frames[0].onerror = function (
message,
source,
lineno,
colno,
error
) {
console.log();
return true;
};
处理页面崩溃和卡顿异常
- 利用 window 对象的 load 和 beforeunload 事件实现了网页崩溃的监控
window.addEventListener('load', function () {
sessionStorage.setItem('good_exit', 'pending');
});
window.addEventListener('beforeunload', function () {
sessionStorage.setItem('good_exit', 'true');
});
if (
sessionStorage.getItem('good_exit') &&
sessionStorage.getItem('good_exit') !== 'true'
) {
alert('页面异常关闭');
}
- 使用 Service Worker 来实现网页崩溃的监控
- Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker 一般情况下不会崩溃;
- Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态;
- 网页可以通过 navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 发送消息。
Script error
一般情况,如果出现 Script error 这样的错误,基本上可以确定是出现了跨域问题。这时候,是不会有其他太多辅助信息的,但是解决思路无非如下:
crossOrigin属性介绍
跨源资源共享机制( CORS ):
为 script 标签添加 crossOrigin属性。
<script src="http://jartto.wang/main.js" crossorigin></script>
特别注意,服务器端需要设置:Access-Control-Allow-Origin
总结:
- 可疑区域增加 Try-Catch
- 全局监控 JS 异常 window.onerror
- 全局监控静态资源异常 window.addEventListener
- 捕获没有 Catch 的 Promise 异常:unhandledrejection
- VUE errorHandler 和 React componentDidCatch
- 监控网页崩溃:window 对象的 load 和 beforeunload
- 跨域 crossOrigin 解决
上报方式:
- 发起一个同步 XMLHttpRequest 来发送数据。或者是一个GET请求或head请求
缺点:1.跨域问题、2.接口挂掉
- 创建一个 img 元素并设置 src
function report(error) {
const reportUrl = 'http://www/baidu.com/report';
new Image().src = `${reportUrl}?logs=${error}`;
}
缺点:大部分用户代理会延迟卸载(unload)文档以加载图像。
优点:
- 没有跨域的限制,可以直接发送跨域的GET请求,不用做特殊处理;
- 兼容性好,一些静态页面可能禁用了脚本,这时script标签就不能使用了;
- 不需要将其append到文档,只需设置src属性便能成功发起请求。*
- 创建一个几秒的 no-op 循环。
上述的所有方法都会迫使用户代理延迟卸载文档,并使得下一个导航出现的更晚。下一个页面对于这种较差的载入表现无能为力。
- navigator.sendBeacon(无阻塞发送统计数据)
- 数据发送是可靠的。
- 数据异步传输。
- 不影响下一导航的载入。
- 数据是通过 HTTP POST 请求发送的。
tips:但由于浏览器兼容性,还是需要用图片的src兜底。
localStorage暂存
localStorage暂存也可以通过localStorage暂存数据,等到浏览器空闲的时候上传,上传成功删除本地数据
大公司是怎么搞的错误监控?看看其他人写的吧:
https://mp.weixin.qq.com/s/kxBObdhfOOh19rlGQ3gHWA
拓展Navigator.sendBeacon()
- Navigator.sendBeacon() 是一个用于异步发送小型数据包的方法,它允许避免延迟敏感数据的丢失。
- 这个方法主要用于满足统计和诊断代码的需要
用法:navigator.sendBeacon(url, data)
- url: 要发送数据包的 URL
- data: 要发送的数据,可以是ArrayBuffer、ArrayBufferView、Blob、DOMString、FormData 或 URLSearchParams 类型的数据。
返回值
sendBeacon() 方法返回一个布尔值:
- true: 数据被异步缓存并将在页面卸载后发送。但这并不保证数据已被成功发送或接收。
- false: 数据无法被缓存,通常是因为数据太大或 URL 无效。
所以我们不能依靠返回值来判断数据是否已被成功发送,它仅表示数据是否已进入发送队列。
原理:
sendBeacon() 方法会创建一个图像对象(图片像素),该对象加载一个包含数据的 URL。这会触发 HTTP 请求并发送数据,但由于它是一个图像,不会延迟页面的 unload 和关闭事件。
虽然 sendBeacon() 是基于图像像素的方式实现的,但它与传统的像素跟踪不同,仅用于发送数据而不收集任何信息。
适用场景:
- 日志和分析数据的收集
- 错误和性能报告
- 用户活动和交互跟踪
数据上报的时机
在会话结束时发送统计数据
网站通常希望在用户完成页面浏览后向服务器发送分析或诊断数据,最可靠的方法是在 visibilitychange 事件发生时发送数据:
document.addEventListener("visibilitychange", function logData() {
if (document.visibilityState === "hidden") {
navigator.sendBeacon("/log", analyticsData);
}
});
使用 pagehide 作为回退
可使用 pagehide 事件来代替部分浏览器未实现的 visibilitychange 事件。和 beforeunload 与 unload 事件类似,这一事件不会被可靠地触发(特别是在移动设备上),但它与 往返缓存(bfcache)兼容。
往返缓存(bfcache):是浏览器为了在用户页面间执行前进后退操作时拥有更加流畅体验的一种策略。该策略具体表现为,当用户前往新页面时,将当前页面的浏览器DOM状态保存到bfcache中;当用户点击后退按钮的时候,将页面直接从bfcache中加载,节省了网络请求的时间
避免使用 unload 和 beforeunload
过去,许多网站使用 unload 或 beforeunload 事件以在会话结束时发送统计数据。然而这是不可靠的,在许多情况下(尤其是移动设备)浏览器不会产生 unload、beforeunload 或 pagehide 事件。
load、unload和pagehide、pageshow的主要应用
1)load 和 unload 事件监听web页面的进入和离开,一般用于页面的首次加载、刷新和关闭等操作的监听;
2)pageshow 和 pagehide 事件多用于监听浏览器的前进和后退等。
示例
一个完整的 sendBeacon() 使用示例:
- 检查要发送的数据大小是否在 64 kB 以内
- 如果是,使用 sendBeacon() 方法异步发送数据
- 根据返回值判断数据是否已进入发送队列
- 如果超过大小限制,在控制台打印错误
- 以避免数据发送失败导致的问题
let logData = { /* object containing info to log */ };
let logUrl = '/log';
let dataLen = JSON.stringify(logData).length;
if (dataLen < 64 * 1024) { // 检查要发送的数据大小是否在 64 kB 以内,如果是,使用 sendBeacon() 方法异步发送数据
let sent = navigator.sendBeacon(logUrl, JSON.stringify(logData));
if (sent) {//根据返回值判断数据是否已进入发送队列,
console.log('Log data queued to be sent to ' + logUrl);
} else {
console.log('Failed to queue log data for sending to ' + logUrl);
}
} else {
console.log('Log data too large to send via Beacon!'); //如果超过大小限制,在控制台打印错误
}
下面列出了一种不触发上述事件的情况:
- 用户加载了网页并与其交互。
- 完成浏览后,用户切换到了其他应用程序,而不是关闭选项卡。
- 随后,用户通过手机的应用管理器关闭了浏览器应用。
此外,unload 事件与现代浏览器实现的往返缓存(bfcache)不兼容。在部分浏览器(如:Firefox)通过在 bfcache 中排除包含 unload 事件处理器的页面来解决不兼容问题,但这存在性能损失。其他浏览器,例如 Safari 和 Android 上的 Chrome 浏览器则采取用户在同一标签页下导航至其他页面时不触发 unload 事件的方法来解决不兼容问题。
Firefox 也会在 bfcache 中排除包含 beforeunload 事件处理器的页面。
安全性
与常规 XHR 请求不同,sendBeacon() 发送的数据不受 CORS 规则的限制,这意味着我们可以将数据发送到任意 URL,甚至是第三方域。
所以,使用 sendBeacon() 方法前,我们必须确保目标 URL 是可信的,不会向其发送敏感数据,避免信息泄露的风险。