一、产生异常的原因
- 逻辑错误
- 业务逻辑判断条件错误
- 事件绑定顺序错误
- 调用栈时序错误
- 错误的操作js对象
- 数据类型出错
- 将null视作对象读取property
- 将undefined视作数组进行遍历
- 将字符串形式的数字直接用于加运算
- 函数参数未传
- 语法句法错误
- 网络错误
- 慢
- 服务端未返回数据但仍200,前端按正常进行数据遍历
- 提交数据时网络中断
- 服务端500错误时前端未做任何错误处理
- 系统错误
- 内存不够用
- 磁盘塞满
- 壳不支持API
- 不兼容
二、错误的分类
- JS运行时错误
- JavaScript代码在用户浏览器中执行时,由于一些边界情况、本地环境的不可控等因素,可能会存在js运行时错误。
- 依赖客户端的某些方法,由于兼容性或者网络等问题,也有概率会出现运行时错误。
- 资源加载错误
- 静态资源包括js、css以及image等。现在的web项目,往往依赖了大量的静态资源,而且一般也会有cdn存在。
- 未处理的promise错误
- 异步请求错误(fetch与xhr)
- 如果使用的是原生的方法,那么需要对错误捕获进行封装
三、捕获方式
-
window.onerror
当JavaScript运行时错误(包括语法错误)发生时,window会触发一个ErrorEvent接口的error事件,并执行window.onerror()。 -
window.addEventListener(‘error’,fn)
当一项资源(如<img>或<script>)加载失败,加载资源的元素会触发一个Event接口的error事件,并执行该元素上的onerror()处理函数。这些error事件不会向上冒泡到window,不过(至少在Firefox中)能被单一的window.addEventListener捕获。 -
window.addEventListener(“unhandledrejection”,fn)
当Promise被 reject 且没有 reject 处理器的时候,会触发unhandledrejection事件;这可能发生在window下,但也可能发生在Worker中。这对于调试回退错误处理非常有用。
// js错误监听
window.onerror = function (msg, url, row, col, error) {
console.warn('js错误监听====>',msg, url, row, col, error);
};
// 网络错误监听
window.addEventListener('error', (error) => {
console.warn('网络错误监听',error);
}, true);
//promise错误监听
window.addEventListener("unhandledrejection", event => {
console.warn(`promise错误监听: ${event.reason}`);
});
四、各个类型错误的捕获方式
-
JS运行时错误
- window.onerror
- window.addEventListener(‘error’)
- 使用window.onerror和window.addEventListener(‘error’)都能捕获,但是window.onerror含有详细的error堆栈信息
-
资源加载错误
- 原理:当一项资源(如<img>或<script>)加载失败,加载资源的元素会触发一个Event接口的error事件,并执行该元素上的onerror()处理函数。
- 这些error事件不会向上冒泡到window,不过能被window.addEventListener在捕获阶段捕获。
- 注意点
- addEventListener也能够捕获js错误,因此需要过滤避免重复上报,判断为资源错误的时候才进行上报
- 不同浏览器下返回的 error 对象可能不同,需要注意兼容处理。
-
Promise错误
- 原理:当promise被reject并且错误信息没有被处理的时候,会抛出一个unhandledrejection。
- 在 promise 中使用 catch 可以非常方便的捕获到异步 error 。没有写 catch 的 Promise 中抛出的错误无法被 或 try-catch 捕获到,所以我们务必要在 Promise 中不要忘记写 catch 处理抛出的异常。
- 这个错误不会被window.onerror以及window.addEventListener(‘error’)捕获,但是有专门的window.addEventListener(‘unhandledrejection’)方法进行捕获处理。
- 为了防止有漏掉的 Promise 异常,建议在全局增加一个对 unhandledrejection 的监听,用来全局监听Uncaught Promise Error。
-
fetch与xhr错误的捕获
- 对于fetch和xhr,我们需要通过改写它们的原生方法,在触发错误时进行自动化的捕获和上报。
-
Vue 错误
Vue.config.errorHandler = (err, vm, info) => { console.error('通过vue errorHandler捕获的错误'); console.error(err); console.error(vm); console.error(info); }
-
React 错误
- componentDidCatch
- error boundary
React 16 提供了一个内置函数 componentDidCatch,使用它可以非常简单的获取到 react 下的错误信息
componentDidCatch(error, info) { console.log(error, info); }
-
iframe 异常
- 对于 iframe 的异常捕获,我们还得借力 window
-
error
- 一般情况,如果出现 error 这样的错误,基本上可以确定是出现了跨域问题。
- 解决方案
- 跨源资源共享机制( CORS )
- 动态去添加 js 脚本
- 服务器端需要设置:Access-Control-Allow-Origin
-
崩溃和卡顿
- 利用 window 对象的 load 和 beforeunload 事件实现了网页崩溃的监控。
- 使用 Service Worker 来实现网页崩溃的监控
五、异常采集
- 采集内容
- 用户信息: 出现异常时该用户的信息,例如该用户在当前时刻的状态、权限等,以及需要区分用户可多终端登录时,异常对应的是哪一个终端。
- 行为信息:用户进行什么操作时产生了异常:所在的界面路径;执行了什么操作;操作时使用了哪些数据;当时的API吐了什么数据给客户端;如果是提交操作,提交了什么数据;上一个路径;上一个行为日志记录ID等。
- 异常信息:产生异常的代码信息:用户操作的DOM元素节点;异常级别;异常类型;异常描述;代码stack信息等。
- 环境信息:网络环境;设备型号和标识码;操作系统版本;客户端版本;API接口版本等。
- 异常捕获
前端捕获异常分为全局捕获和单点捕获。全局捕获代码集中,易于管理;单点捕获作为补充,对某些特殊情况进行捕获,但分散,不利于管理。-
全局捕获
通过全局的接口,将捕获代码集中写在一个地方,可以利用的接口有:- window.addEventListener(‘error’) / window.addEventListener(“unhandledrejection”) 等
- 框架级别的全局监听,例如aixos中使用interceptor进行拦截,vue、react都有自己的错误采集接口
- 通过对全局函数进行封装包裹,实现在在调用该函数时自动捕获异常
- 对实例方法重写(Patch),在原有功能基础上包裹一层,例如对console.error进行重写,在使用方法不变的情况下也可以异常捕获
-
单点捕获
在业务代码中对单个代码块进行包裹,或在逻辑流程中打点,实现有针对性的异常捕获:- try…catch
- 专门写一个函数来收集异常信息,在异常发生时,调用该函数
- 专门写一个函数来包裹其他函数,得到一个新函数,该新函数运行 结果和原函数一模一样,只是在发生异常时可以捕获异常
-
跨域脚本异常
由于浏览器安全策略限制,跨域脚本报错时,无法直接获取错误的详细信息,只能得到一个Script Error。例如,我们会引入第三方依赖,或者将自己的脚本放在CDN时。
-