美文网首页
前端到底有多少种异常?如何以最佳实践处理它们?

前端到底有多少种异常?如何以最佳实践处理它们?

作者: microkof | 来源:发表于2020-10-19 13:35 被阅读0次

前言

一名初级前端程序员,面对异常往往是佛当杀佛,神挡杀神,最后很累,脑子却始终没有一个清晰的概念,这样是不行的。

今天我整理一下,前端到底有多少种异常,以及它们的处理方式最佳实践。

异常分类

前端能避免的异常,就必须百分百避免:

  • JS语法错误、书写代码异常

  • Promise异常

  • 其他崩溃和卡顿

前端无法绝对避免的异常:

前端无法绝对避免的异常要么来自于服务器,要么来自于网络连接。比如:

  • 静态资源(js、css、图片、svg、字体、其他未列举的资源)加载异常

  • Iframe加载页面异常

  • 跨域错误

  • AJAX异常

一、JS语法错误、运行时异常

先弄清2个概念,JS语法错误和运行时错误。

  • 语法错误就是书写错误,比如少个括号,少个引号,在代码运行前,JS引擎进行代码分析的时候就会发现。即便异常出现在函数里,即便需要靠触发运行,也一样会被提前发现,更何况脚手架里大多自备语法分析工具,所以并不担心语法错误。

  • 运行时错误就是JS引擎对语法的分析已经通过,JS引擎开始运行,运行到某处时报错,比如变量未定义(可能是多敲了一个字符)、视图访问undefined的属性,当然也会报异常。依靠触发的函数,里面有异常的话,直到它被触发,才会报错。前端程序员应当关心运行时错误。

console.log('log')
a.b.c = '1' // 这叫运行时错误,会说a未定义,如果单只有这句在,没有下面那句,可以打印log
a.b.c = '1 // 这叫语法错误,少个引号,有这句在,就不会打印log

这2种异常在开发阶段和测试阶段大多能发现,尤其是语法错误。发现之后修复就好了。但在一些极限场景下,依然可能报出一些异常,这是为什么?

  1. 后端接口的数据有错误。所以后端数据不可信,所以该做的判断一定不能少。

  2. 模块js文件加载失败。

  3. 打包过程中优化错误导致。

  4. npm包升级之后带来新错误,没有及时发现。

怎么解决?

  1. 维护服务器和线路稳定。

  2. 不要在发包的前几天升级npm,起码应当有一个月来检验新的npm包是否有问题。

  3. 应当捕获错误,并简明扼要的提示。

大公司对于前端异常都有完善的捕获和上报系统,这个系统说起来太复杂,我也没见过,我只说说小项目的捕获和提示。

讨论try...catch...

它只能捕获同步代码的运行时异常,不能捕获异步的运行时异常。在如今的项目中,异步执行的代码比同步执行的代码要多得多,非要加try...catch...的话,会遍地都是try...catch...,会让开发者和维护者彻底疯掉。

所以try...catch...的使用场合:

  1. 尽量避免使用try...catch...。

  2. 只在最小范围使用try...catch...。

  3. 只在能预见的运行时错误场合使用try...catch...,比如我只关心a.b.c.d是不是等于3,如果不等于,哪怕a.b.c.d有运行时错误,我也不关心,这种场景,可以用try...catch...。但必须注意,依然要尽量避免使用try...catch...,这个案例最好还是依次判断一下(a && a.b && a.b.c && a.b.c.d && a.b.c.d === 3),更何况,ECMAScript 2020引入了可选链操作符(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/可选链)。

讨论window.onerror

window.onerror的捕获特点是:

  1. 只能捕获到运行时错误,原因上面说过,语法错误就根本通不过JS的代码分析,也不可能运行。

  2. 可以捕获同步代码和异步代码的异常。

  3. 主要是来捕获预料之外的错误。

  4. 捕获不到静态资源加载异常。

  5. 尽量写在项目所有代码的第一行。

讨论window.addEventListener('error', (error) => {})

它跟window.onerror的区别是:

  1. window.onerror是标准错误捕获接口,window.addEventListener('error')不那么标准,各个浏览器的实现不统一,拿到的数据也不完整。

  2. window.onError虽然标准但是不能捕获资源加载失败,window.addEventListener('error')就可以。

  3. 需要注意避免window.addEventListener重复监听。window.onerror不存在重复监听的现象。

使用场合:

  1. 重试静态资源加载。js如果发现静态资源加载失败,并不需要向用户弹提示,因为弹了提示也无法引导用户把资源加载成功。但是js至少可以重试一次加载,万一第二次加载成功了呢。

  2. 上报的作用更大一些,依靠这个可以知道静态资源是否偶尔失效。

讨论Promise的异常和window.addEventListener('unhandledrejection', (error) => {})

Promise的.catch回调、.then()的第二个回调,都能捕获2种错误:运行时错误和reject抛出的错误。

运行时错误未被捕获,会报Uncaught (in promise) xxxError错误。

reject的错误未被捕获,会报Uncaught (in promise)错误。

我们说过,能预见的运行时错误都应该当场最小范围利用try...catch...解决,而不是去捕获。不能预见的运行时错误,原则上我们希望0错误,即使有,也希望接近于0,对于不可预见的运行时错误,我们一定要使用统一的捕获:

window.addEventListener("unhandledrejection", function(e){
  console.log(e);
});

unhandledrejection事件的使用场合:

  1. 推荐放在项目代码的第一行。

  2. 即便捕获到了,控制台依然会打印错误,如果不想让控制台打印错误,则在unhandledrejection事件函数末尾加上event.preventDefault();

  3. 对于捕获的运行时错误,可以对用户弹提示,还可以上报。

  4. 对于reject错误,也分为2部分,一部分是可预见的错误,一部分是不可预见的错误。百分之99的Promise场合都是在Ajax,所以我们在下文Ajax部分继续讨论。

讨论Vue.config.errorHandler

官方手册:https://cn.vuejs.org/v2/api/#errorHandler

终于遇到一个封装好的错误处理机制了,它的使用就不在此多说了。

讨论iframe页面错误

iframe引用页面错误,又分为2种情况:

  1. 如果域名都是错的,收不到响应,这种情况下,Chrome控制台不会有任何报错,原因是iframe天生允许跨域,对方服务器有可能不归你管,对方没有任何响应不算错误。

  2. 如果服务器正确,且返回404,Chrome控制台会报错,但是无法捕获,原因是404是合法的状态码,Chrome控制台的报错应视为提示而不是错误。此时window.frames[0].onload会触发,onerror不会触发。

变相捕获的办法只能是设window.frames[0].onload,然后看e.target.document.body里的内容是报错内容还是正常内容。

考虑到如今iframe的使用场合太少,出错的话影响大,也没有弹提示的意义,所以应将重心放在保证服务正常运行上,不考虑捕获和上报。

讨论跨域错误

就2020年的开发来讲,跨域错误百分之98是Ajax跨域错误,还有极低概率是iframe跨域错误和其他一些跨域错误。

跨域错误理论上在开发阶段就会被发现,非要说生产阶段出现跨域错误,无非有2种可能:

  1. 开发者不小心将正确的CORS跨域配置修改成了错误的跨域配置,还不自知。

  2. 前端发送了某种写错的请求,后端框架没有考虑到这种错误,也没有配置跨域,于是没有返回允许跨域的响应头。

跨域错误跟上面提到的Promise reject错误都放到最下面的Ajax错误里一并讨论。

讨论Ajax错误

Ajax错误是事实上前端最需要关注的错误。由于世上的所有Ajax解决方案都采用了Promise封装,所以开发者需要思考的问题已经很少了,Ajax错误事实上都被Ajax库封装为Promise错误,同时Ajax库已经解决了相当多的出错可能。

  1. 跨域

跨域被axios库归为Network Error,axios的源码是:

image.png

你可以在axios响应拦截器的第二个回调里捕获这个错误,然后修改成网络出错并弹出,毕竟中国人用中文。

  1. 断网

断网也会被axios处理为Network Error,你也弹出网络错误就得了。

  1. 服务器非200的响应

非200的响应,最出名的就是:403、404、500这三个,还可以加其他的状态码,就看你的团队是怎么约定的。

道理上说,200视为Promise的resolve,其他一律视为reject。那么有个问题就是什么情况应当归为200响应。

我认为,应当把合乎预料的反馈视为200,出乎预料的视为非200。

什么叫合乎预料?

一个List,比如用户的日记,0篇很正常,10篇也很正常,都是业务允许的,那么,服务器返回

{
  list: []
}

或者

{
  list: null
}

都是允许的,业务代码都需要考虑到,所以状态码都应当是200。

除此之外的任何情形,都应当视为出乎预料的,应设为非200,比如403、404、500等等。比如根本没有这个用户,你却要请求它的日记,要么返回404,要么返回500,看团队内部怎么定。

现在有一个话题,我们讨论一个场景。

有些API,后端给的响应状态码是500,响应体是这样的:

{
  "msg": “某某错误某某错误”
}

前端程序员觉得这个msg给的特别棒,可以直接弹个提示,于是他就写了:

// 伪代码
Notify({
  content: err.msg
})

但是有时候,前端又觉得后端给的msg写的稀烂,不适合直接弹,于是前端自己写了一个:

// 伪代码
Notify({
  content: '某某错误某错'
})

这时候前端就在想:content: err.msg是千篇一律的,我总要写一遍这个,是不是很浪费时间浪费空间?于是他想了一个主意:

凡是打算弹content: err.msg,就不写它,而且干脆连错误捕捉都不写。是不是省了N多的代码?

那么,谁负责弹提示呢?就让window.addEventListener("unhandledrejection", function(e){console.log(e);});负责就好啦!

下方代码中,Notify由于是伪代码,我为了能跑通,所以注释掉了。new Promise是为了模拟axios返回了reject,then我只写了成功回调,意思是省略失败回调。其结果:成功打印了失败原因。

window.addEventListener("unhandledrejection", function(e){
  // Notify((
  //   content: e.reason.msg;
  // ))
  console.log(e.reason.msg);
  event.preventDefault();
});
new Promise((resolve, reject) => {
  reject({msg: '某某错误某某错误'})
}).then(() => {

});

也就是说:凡是能统一处理的失败回调,都可以直接省掉失败回调,让window.addEventListener("unhandledrejection", fn)去兜底就可以了。

那么,是否能在拦截器里统一处理失败回调的弹出呢?当然不行了,拦截器又不知道哪些msg写的漂亮,哪些写的稀烂。

上报

上报通常不采用Ajax,因为Ajax本身也有概率出问题,而且有概率报跨域,而且会抢正常业务的线程,所以业界一般是用new Image()动态赋值带参数的src来向服务器发请求。大公司的整套方案具体就不多说了,我也没见过,可以看看https://mp.weixin.qq.com/s/kxBObdhfOOh19rlGQ3gHWA

相关文章

  • 前端到底有多少种异常?如何以最佳实践处理它们?

    前言 一名初级前端程序员,面对异常往往是佛当杀佛,神挡杀神,最后很累,脑子却始终没有一个清晰的概念,这样是不行的。...

  • 文章收藏

    vue中H5问题汇总移动端最佳实践前端知识图谱vue-cli4-configGraphQL入门前端异常处理最佳实践...

  • 前端异常处理最佳实践

    前端可以说是最贴近用户的一层,当产品不断的迭代完善,产品的用户体验会更加趋向于完美,然而前端异常却是很另人头疼的一...

  • 前端异常处理最佳实践

    前端可以说是最贴近用户的一层,当产品不断的迭代完善,产品的用户体验会更加趋向于完美,然而前端异常却是很另人头疼的一...

  • Java 最佳实践的经验

    Java 最佳实践的面试问题 包含 Java 中各个部分的最佳实践,如集合,字符串,IO,多线程,错误和异常处理,...

  • Spring Boot统一异常处理实践

    摘要: SpringBoot异常处理。 原文:Spring MVC/Boot 统一异常处理最佳实践 作者:赵俊 前...

  • springboot 统一异常处理 自定义异常返回

    看完以下三篇就懂了: Spring MVC/Boot 统一异常处理最佳实践讲了异常处理的正确思路:service/...

  • springboot 异常处理最佳实践

    通常情况下,在一个系统中,抽象出公用的部分的过程是一个DRY的一个过程。这是所有计算机工程中的一个准则。防止重复是...

  • Java异常处理最佳实践

    在 Java 中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考...

  • Java异常处理最佳实践

    我们为什么要做异常处理 1、给请求端明确的操作指导。 2、正确记录系统异常时的完整场景,包括代码的调用过程、出错点...

网友评论

      本文标题:前端到底有多少种异常?如何以最佳实践处理它们?

      本文链接:https://www.haomeiwen.com/subject/kujoyktx.html