关于前端:前端开发之异常捕获技巧

5次阅读

共计 6785 个字符,预计需要花费 17 分钟才能阅读完成。

作为一个前端开发人员,每次看到浏览器控制台信息外面红通通的报错信息是不是都很缓和 …… 不要怕,上面咱们就来讨论一下前端的异样捕捉。

异样捕捉,绝对于其余知识点可能没那么被器重,特地是对于前端程序员。但不得不说,这又是一个不得不面对的知识点。

为什么要捕捉异样

首先,咱们为什么要进行异样捕捉和上报呢?

正所谓百密一疏,用程序员的话来说就是:天下不存在没有 bug 的程序(不承受反驳 🤐)。即便通过各种测试,还前端培训是会存在非常荫蔽的 bug,这种不可预感的问题只有通过欠缺的监控机制能力无效的缩小其带来的损失。因而,对于最靠近用户的前端来说,为了能近程定位问题、加强用户体验,异样的捕捉和上报至关重要。

目前市面上曾经有一些十分欠缺的前端监控零碎存在,如 Fundebug、Bugsnag 等,尽管这些曾经能做到帮咱们实时监控生产环境的异样,然而如果咱们不理解异样是如何产生的,又怎么能得心应手的定位并解决问题呢?

对于 JS 而言,咱们面对的仅仅只是异样,异样的呈现不会间接导致 JS 引擎解体,最多只是终止以后代码的执行。上面来解释一下这句话:

<script>
error // 没定义过的变量,此处会报错
console.log(‘ 永远不会执行 ’);
</script>
<script>
console.log(‘ 我继续执行 ’)
</script>

异样捕捉分类

这里我做了一个脑图演绎一些前端异样,不肯定对,只是有个大略印象。如下:

上面就针对不同异样的捕捉一一剖析:

try…catch 的误区

try…catch 只能捕捉到同步的运行时谬误,对于语法和异步谬误无能为力,捕捉不到。

1. 同步运行时谬误

try {let name = ‘Jack’;console.log(nam);} catch(e) {console.log(‘ 捕捉到异样:’,e);}

输入:

捕捉到异样:ReferenceError: nam is not definedat <anonymous>:3:15

2. 不能捕捉语法错误,咱们批改一个代码,删掉一个单引号

try {let name = ‘Jack;console.log(nam);} catch(e) {console.log(‘ 捕捉到异样:’,e);}

输入:

Uncaught SyntaxError: Invalid or unexpected token

语法错误 SyntaxError,不论是 window.error 还是 try…catch 都没法捕捉异样。然而不必放心,在你写好代码按下保留那一刻,编译器会帮你查看是否有语法错误,如果有谬误有会有个很显著的红红的波浪线,把鼠标移上去就能看到报错信息。因而,面对 SyntaxError 语法错误,肯定要小心小心再小心

3. 异步谬误

try {setTimeout(() => {undefined.map(v => v);}, 1000)} catch(e) {console.log(‘ 捕捉到异样:’,e);}

输入:

Uncaught TypeError: Cannot read property ‘map’ of undefinedat setTimeout (<anonymous>:3:11)

能够看到,并没有捕捉到异样。

window.onerror 不是万能的

当 JS 运行时谬误产生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()。

/* @param {String} message 错误信息 @param {String} source 出错文件 @param {Number} lineno 行号 @param {Number} colno 列号 @param {Object} error Error 对象(对象)*/window.onerror = function(message, source, lineno, colno, error) {console.log(‘ 捕捉到异样:’,{message, source, lineno, colno, error});}

1. 同步运行时谬误

window.onerror = function(message, source, lineno, colno, error) {// message:错误信息(字符串)。// source:产生谬误的脚本 URL(字符串)// lineno:产生谬误的行号(数字)// colno:产生谬误的列号(数字)// error:Error 对象(对象)console.log(‘ 捕捉到异样:’,{message, source, lineno, colno, error});}UndefVar;

能够看到,咱们捕捉了异样:

2. 语法错误

window.onerror = function(message, source, lineno, colno, error) {console.log(‘ 捕捉到异样:’,{message, source, lineno, colno, error});}let name = ‘Jack; // 少个单引号

控制台打印出了这样的异样:

Uncaught SyntaxError: Invalid or unexpected token

能够看出,并没有捕捉到异样。

3. 异步运行时谬误

window.onerror = function(message, source, lineno, colno, error) {console.log(‘ 捕捉到异样:’,{message, source, lineno, colno, error});}setTimeout(() => {UndefVar;});

同样看到,咱们捕捉了异样:

4. 网络申请的异样

<script>window.onerror = function(message, source, lineno, colno, error) {console.log(‘ 捕捉到异样:’,{message, source, lineno, colno, error});return true;}</script><img src=”./xxx.png”>

咱们发现,不论是动态资源异样,或者接口异样,谬误都无奈捕捉到。

留神:

1.window.onerror 函数只有在返回 true 的时候,异样才不会向上抛出(浏览器接管后报红),否则即便是晓得异样的产生控制台还是会显示 Uncaught Error: xxxxx

2.window.onerror 最好写在所有 JS 脚本的后面,否则有可能捕捉不到谬误

3.window.onerror 无奈捕捉语法错误

那么问题来了,如何捕捉动态资源加载谬误呢?

window.addEventListener

当一项资源(如图片和脚本加载失败),加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的 onerror 处理函数。这些 error 事件不会向上冒泡到 window,不过(至多在 Chrome 中)能被繁多的 window.addEventListener 捕捉。

<script>window.addEventListener(‘error’, (error) => {console.log(‘ 捕捉到异样:’, error);}, true)</script><img src=”./xxxx.png”>

能够捕捉异样:

因为网络申请异样不会事件冒泡,因而必须在捕捉阶段将其捕捉到才行,然而这种形式尽管能够捕捉到网络申请的异样,然而无奈判断 HTTP 的状态是 404 还是其余比方 500 等等,所以还须要配合服务端日志才进行排查剖析才能够。

留神:

不同浏览器下返回的 error 对象可能不同,须要留神兼容解决。

须要留神防止 window.addEventListener 反复监听。

到此为止,咱们学到了:在开发的过程中,对于容易出错的中央,能够应用 try{}catch(){}来进行谬误的捕捉,做好兜底解决,防止页面挂掉。而对于全局的谬误捕捉,在古代浏览器中,我偏向于只应用应用 window.addEventListener(‘error’),window.addEventListener(‘unhandledrejection’)就行了。如果须要思考兼容性,须要加上 window.onerror,三者同时应用,window.addEventListener(‘error’)专门用来捕捉资源加载谬误。

Promise Catch

咱们晓得,在 promise 中应用 catch 能够十分不便的捕捉到异步 error。

没有写 catch 的 promise 中抛出的谬误无奈被 onerror 或 try…catch 捕捉到,所以务必在 promise 中写 catch 做异样解决。

有没有一个全局捕捉 promise 的异样呢?答案是有的。Uncaught Promise Error 就能做到全局监听,应用形式:

window.addEventListener(“unhandledrejection”, function(e){// e.preventDefault(); // 阻止异样向上抛出 console.log(‘ 捕捉到异样:’, e);});Promise.reject(‘promise error’);

同样能够捕捉谬误:

所以,正如咱们下面所说,为了避免有漏掉的 promise 异样,倡议在全局减少一个对 unhandledrejection 的监听,用来全局监听 Uncaught Promise Error。

iframe 异样

对于 iframe 的异样捕捉,咱们还得借力 window.onerror:

window.onerror = function(message, source, lineno, colno, error) {console.log(‘ 捕捉到异样:’,{message, source, lineno, colno, error});}

上面一个简略的例子:

<iframe src=”./iframe.html” frameborder=”0″></iframe><script>window.frames[0].onerror = function (message, source, lineno, colno, error) {console.log(‘ 捕捉到 iframe 异样:’, {message, source, lineno, colno, error});};</script>

Script error

在进行谬误捕捉的过程中,很多时候并不能拿到残缺的错误信息,失去的仅仅是一个 ”Script Error”。

产生起因

因为 12 年前这篇文章里提到的平安问题:https://blog.jeremiahgrossman… 当加载自不同域的脚本中产生语法错误时,为防止信息泄露,语法错误的细节将不会报告,而是应用简略的 ”Script error.” 代替。

一般而言,页面的 JS 文件都是放在 CDN 的,和页面本身的 URL 产生了跨域问题,所以引起了 ”Script Error”。

解决办法

个别状况,如果呈现 Script error 这样的谬误,基本上能够确定是跨域问题。这时候,是不会有其余太多辅助信息的,然而解决思路无非如下:

跨源资源共享机制(CORS):咱们为 script 标签增加 crossOrigin 属性。

<script src=”http://jartto.wang/main.js” crossorigin></script>

解体和卡顿

卡顿也就是网页临时响应比较慢,JS 可能无奈及时执行。但解体就不一样了,网页都解体了,JS 都不运行了,还有什么方法能够监控网页的解体,并将网页解体上报呢?

1. 利用 window 对象的 load 和 beforeunload 事件实现了网页解体的监控。

window.addEventListener(‘load’, function () {sessionStorage.setItem(‘good_exit’, ‘pending’);setInterval(function () {sessionStorage.setItem(‘time_before_crash’, new Date().toString());}, 1000);});window.addEventListener(‘beforeunload’, function () {sessionStorage.setItem(‘good_exit’, ‘true’);});if(sessionStorage.getItem(‘good_exit’) &&sessionStorage.getItem(‘good_exit’) !== ‘true’) {/insert crash logging code here/alert(‘Hey, welcome back from your crash, looks like you crashed on: ‘ + sessionStorage.getItem(‘time_before_crash’));}

2. 基于以下起因,咱们能够应用 Service Worker 来实现网页解体的监控:

2.1Service Worker 有本人独立的工作线程,与网页辨别开,网页解体了,Service Worker 个别状况下不会解体

2.2Service Worker 生命周期个别要比网页还要长,能够用来监控网页的状态

2.3 网页能够通过 navigator.serviceWorker.controller.postMessage API 向主持本人的 SW 发送音讯

VUE errorHandler

在 Vue 中,异样可能被 Vue 本身给 try…catch 了,不会传到 window.onerror 事件触发。不过不必放心,Vue 提供了特有的异样捕捉,比方 Vux2.x 中咱们能够这样用:

Vue.config.errorHandler = function (err, vm, info) {let { message, // 异样信息 name, // 异样名称 script, // 异样脚本 urlline, // 异样行号 column, // 异样列号 stack // 异样堆栈信息} = err;// vm 为抛出异样的 Vue 实例 // info 为 Vue 特定的错误信息,比方谬误所在的生命周期钩子}

React 异样捕捉

在 React,能够应用 ErrorBoundary 组件包含业务组件的形式进行异样捕捉,配合 React 16.0+ 新出的 componentDidCatch API,能够实现对立的异样捕捉和日志上报。

咱们来举一个小例子,在上面这个 componentDIdCatch(error,info) 里的类会变成一个 error boundary:

class ErrorBoundary extends React.Component {constructor(props) {super(props);this.state = {hasError: false};}componentDidCatch(error, info) {// Display fallback UIthis.setState({ hasError: true});// You can also log the error to an error reporting servicelogErrorToMyService(error, info);}render() {if (this.state.hasError) {// You can render any custom fallback UIreturn <h1>Something went wrong.</h1>;}return this.props.children;}}

而后咱们像应用一般组件那样应用它:

<ErrorBoundary> <MyWidget /></ErrorBoundary>

componentDidCatch() 办法像 JS 的 catch{} 模块一样工作,然而对于组件,只有 class 类型的组件 (class component) 能够成为一个 error boundaries。

实际上,大多数状况下咱们能够在整个程序中定义一个 error boundary 组件,之后就能够始终应用它了!

须要留神的是:error boundaries 并不会捕获上面这些谬误:

  1. 事件处理器
  2. 异步代码
  3. 服务端的渲染代码
  4. 在 error boundaries 区域内的谬误

总结

可疑区域减少 try…catch

全局监控 JS 异样:window.onerror

全局监控动态资源异样:window.addEventListener

全局捕捉没有 catch 的 promise 异样:unhandledrejection

iframe 异样:window.error

VUE errorHandler 和 React componentDidCatch

监控网页解体:window 对象的 load 和 beforeunload

Script Error 跨域 crossOrigin 解决

本文作者:奔跑的瓜牛

正文完
 0