You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
JS 运行时错误(变量未声明,Can't read property 'xxx' of undefined 等)
Promise 抛出异常
外部脚本异常(如iframe,跨域脚本)
资源加载异常
常见异常示例
<script>
error
console.log('You wont see me.')
</script>
<script>
console.log('You will see me!!!')
</script>
输出
Uncaught ReferenceError: error is not defined
You will see me!!!
这里两个 script 标签属于两个不同的代码块,属于不同的宏任务,(JS 单线程的工作方式就是不停的去队列将任务取出来执行,具体细节参考 JS 的事件循环机制,本文不展开说明)因此可以看到第一个 log 被 JS 报错给阻断了,异常没有被捕获,但是这并不影响第二个任务块的执行
下面讨论下针对对以上异常的捕获方式
1. try-catch
最熟悉不过的就是 try-catch,还是用上面的例子
<script>
try{
error
}
catch(err) {
console.log('We Caught Error -> ', err)
}
console.log('Check if you can see me')
</script>
<script>
console.log('You will see me!!!')
</script>
输出
We Caught Error -> ReferenceError: error is not defined at error-script-demo.html:14
Check if you can see me
You will see me!!!
这时异常被 catch 掉了,所以不会阻断当前任务的执行,两个 log 都正常输出
另外 try-catch 无法捕获 JS 语法错误,举个🌰
<script>
try{
function()
}
catch(err) {
console.log('We Caught Error -> ', err)
}
console.log('Check if you can see me')
</script>
<script>
console.log('You will see me!!!')
</script>
随便写了个错误的写法,来看输出
Uncaught SyntaxError: Function statements require a function name
You will see me!!!
前言
为降低脚本执行的错误率,更快定位前端异常的情况,很多前端团队都有自己的前端监控系统,市面上也有一些颇受好评的第三方异常监控系统,如 sentry. 本文结合自己的一些见解和实践,围绕下面几个大的方向去讨论。
异常捕获/处理
前端的常见的异常主要分为两类,JS 脚本异常及网络相关异常
常见异常示例
输出
下面讨论下针对对以上异常的捕获方式
1. try-catch
最熟悉不过的就是 try-catch,还是用上面的例子
输出
另外 try-catch 无法捕获 JS 语法错误,举个🌰
随便写了个错误的写法,来看输出
可以看到第一个任务中的异常没有被捕获到,从而后面的代码被阻断了
这种语法错误类型的异常,只要在项目中像使用类似 ESlint 这种代码检测工具,一般都能提前发现并避免,否则就需要好好检查自己项目的开发流程及测试流程了
同时 try-catch 对异步的异常也无法进行捕获,如下
输出
try-catch 我们常用于 JS 运行时可预知可能会出现错误的地方捕获异常,通常是针对特定的业务场景,针对性比较强,且对代码的执行效率有所影响,因此在一个项目中,并不会大量使用。
window.onerror
window.onerror 是一个全局异常捕获事件,但能够捕获到未被捕获的脚本异常
注意,是未被捕获
window.onerror 可以捕获到语法错误(在 Chrome 浏览器测试,版本 80.0.3987.132(正式版本) 64 位,自己测试是可以的,但是在网上看到一些文章说不可以,具体自行测试吧),也可以捕获到异步产生的异常,同时在其回调函数上,有充足的异常信息,可以看出,它比 try-catch 功能要更强大一些
输出
另外 window.onerror 无法捕获到 http 异常,比如图片加载失败,示例
控制台只有一条异常输出,这是浏览器自己输出的,我们并没有捕获到
从上面可以看到,即使我们已经通过 window.onerror 方法捕获了异常,但是浏览器还是会输出自己的异常打印信息(浏览器默认的,并非我们手动打印的信息),此时我们可以通过 return true 来阻止其在控制台打印额外的信息,默认返回值是 false
更多见 MDN 上详细说明 GlobalEventHandlers.onerror
window.addEventListener
对于一些 http 请求相关的异常,我们可以通过 addEventListener 给 window 注册 error 事件
可以看到图片请求失败的异常被捕获到了
注意 addEventListener 的第三个参数,它的参数名为 useCapture,我把它设置为 true(默认为 false),因为 http 相关异常不会事件冒泡,因此必须在其捕获阶段将其捕获,但是 EventListener 捕获不了 http 的状态码,也就是 404, 500 这些都拿不到,还是得配合后端接口日志来进行排查
注意:
因为 window.onerror 有详细的调用栈信息,更适合去捕获一些脚本方面的异常,而 addEventListener error 事件,更适合捕获一些网络加载相关的异常,因此可以加以区分
Promise catch
输出
对于 Promise 抛出的异常,之前使用的 try-catch, window.onerror 和 addEventListener error 都无法捕获,只能够使用 Promise 自己的 catch 方法以及全局提供的 unhandledrejection 来捕获。
crossorigin
对于一些跨域的外部脚本,由于同源策略限制,如果其执行出现异常的话,会直接报 Script Error,没有其他任何信息。
解决思路无非两种
方式 1 简单粗暴解决了问题,但是也带来更大的问题,无法利用好文件缓存和 CDN 的优势
接下来主要说说方式2
先来看看具体问题,这里我引用了一个外部脚本,这个脚本对其他库有依赖,我没有引就会报错
可以看到 window.onerror 和 addEventListener error 事件都只能拿到一个 Script Error 错误信息
跨域资源共享,可以通过给 script 加 crossorigin 属性,增加 crossorigin 属性后,浏览器将自动在请求头中添加一个 Origin 字段,发起一个 跨来源资源共享 请求。Origin 向服务端表明了请求来源,服务端将根据来源判断是否正常响应,若为合法请求,后端在响应头上设置
Access-Control-Allow-Origin
,否则依然会报跨域异常这时再强制刷新一下(注意是强制,因为有可能会读缓存从而继续报错)
这时可以看到,已经有完整的报错信息了。
iframe 异常
我们来看下用之前的异常捕获能否捕获到 iframe 中的异常
可以看到,控制台有报错,但只有一条浏览器自己的报错信息,并没有触发我的事件声明
那如何监听到 iframe 里面的错误呢?
对于是同源的 iframe ,我们可以通过 window.frames 这个对象拿到页面上的 iframe 对象,它是一个类数组对象,我们可以这样:
成功捕获到异常
但是对于非同源的,这种方法是行不通的,需要使用到一些额外的通信手段了,因为对 iframe 不是很熟悉,所以就不详情展开了,具体可以参考文章 跨域,你需要知道的全在这里
另外还有一些框架相关的异常捕获 api,像 react 的 Error Boundaries,这里不一一展开说明,自行查看官方文档即可。
异常上报
前面介绍了这么多异常的类型和捕获的方法,那前端拿到了异常信息之后,要怎么上报呢?常见的有两种方案
第1种就是封装个接口,像平时发送请求一样发送数据,但是鉴于 ajax 本身也可能会产生异常,所以大家还是第2种方式居多
方式2 利用的是 img 标签的 src 属性,它可以发起一个单向 get 请求,因为上报日志我们并不需要获取响应数据,所以单向即可满足要求(也许会节省带网络带宽什么的,但我没有求证),并且 src 还具备跨域能力,非常方便,业界很多大厂像淘宝,京东都是使用 img src 来发送请求
参考:
前端监控体系怎么搭建?
脚本错误量极致优化-监控上报与Script error
The text was updated successfully, but these errors were encountered: