详解JS中异常与错误处理的正确方法
作者:可畏
简介
首先,这篇文章一定会引起争议,因为对于错误处理从来就没有真正的标准答案,每个人都会有自己的主观意见。 我的理解毕竟也是片面,提出的想法主要是基于个人的经验总结,如果有异议,欢迎交流讨论。 为了能够尽量保持客观,我会将处理思想尽量前置,再围绕处理思想展开。 这样大家就能先思考这个思想是否能够成为前提,以及在这个思想前提下的最优处理方式。
1 面向错误编程
为什么说面向错误编程,这里就要向大家发出灵魂一问,你能确保你写的每行代码都是正常的吗? 假设你的代码天衣无缝,但你能确保你调用别人提供的方法也不会出现问题吗? 显然,我们无法保证每行代码的正确性,也无法保证调用别人的方法一定能成功执行。所以如何处理错误就成了重中之重。 这时候可能就有人说了:“当然有可能会出现问题啊,不然要测试干嘛?”我们需要知道的是,当这个错误暴露出来的时候, 可能就造成了无法估量的损失。测试人员在进行测试的时候是不知道我们的代码是如何写的,是很有可能测试时候漏了一些点的, 而这每个遗留点都会成为一个个地雷,说不定哪天就爆了。这也是为什么很多公司都特别重视单元测试的原因。 关于单元测试我们回头专门讲解,今天主要讨论面向错误编程。为了防止出现极端情况,我们需要先考虑错误情况,再去考虑正常。 这种编程思想我们将其称之为面向错误编程。
1.1 墨菲定律
墨菲定律讲的是一个事件如果有两种或两种以上的方式去做某件事情,而其中一种方式将导致灾难,则必定有人会做出这种选择。 就像一艘船足够稳固,翻船的概率几乎为零,但是它还是有翻船的可能性,所以就得备好救生衣。 同样,即使你的代码出错概率为百万分之一,但是那百万分之一一旦发生,对使用者来说就是100%,所以必须做好应急策略。
1.2 先判否
在代码出错这点上,我们可以假设一个极端情况,即我们写的每行代码都有可能出错。 所以在写代码之前我们就需要考虑到可能出现错误的情况,在代码上的体现就是我们可能先写了一堆错误的处理,最后才去处理正确的逻辑。 面向错误编程的核心技巧就是先判否。
function doSomeThing (params) { if (!params.a) return if (params.b !== true) return // ... // 开始写正确代码逻辑 }
错误不是异常
我们需要明确,异常是代码运行时发生的异常信息,如果不处理会导致代码无法运行。而错误是代码在运行过程中一个不期待的结果。 比如发起一个 http 请求,即使出现网络出错等情况导致请求失败,但是请求即使失败了,也应该是不影响代码继续运行的。 对于我们来说一个请求只有一个结果,要么成功,要么失败,而失败其实也是一个结果,那么这就是一个错误。 但是在执行 JSON.parse 方法的时候如果解析出错不做处理,就会因为解析异常而导致代码运行终止,那么这就是一个异常。
2. js 内置的错误处理
2.1 Error 类
当运行时错误产生时,Error 对象会被抛出。Error 对象也可用于用户自定义的异常的基础对象。js还封装了一些内置的标准错误类型。 如语法错误的 SyntaxError,类型错误的 TypeError,以及 ReferenceError、RangeError、URIError、AggregateError、AggregateError、InternalError。
2.2 throw
throw 用于抛出一个异常。经常结合 Error 一起使用。
2.3 try catch
try catch 用于捕获一个可能出现的未知异常。刚刚提到的 JSON.parse 就是一个最好的例子,在解析一个字符串的时候, 你没法确保字符串就一定是一个 json 字符串,所以必须使用 try catch 包裹异常,不然就会发生代码运行终止。 而我们很多同学在 try catch 的使用过程中经常会发生滥用现象,比如使用 try catch 包裹请求错误:
try { const res = await request() if (res) { //... } } catch(err) { // ... // 请求错误处理 }
我个人非常反对这种使用,为什么呢,按照刚刚说的,请求结果其实是一个值,即使请求失败,返回的也是一个值。 而在 try 里面即使拿到请求成功的值,在后续处理 res 的过程中如果发生语法错误,这时也会抛出异常, 那此时你还能确定这个 catch 捕获到的异常是请求失败了还是处理 res 时候报错的吗? 我之前说过一句话,滥用 try catch 和随地大小便无异。所以希望大家不要再使用这种方法。
2.4 Promise.catch
js 中还提供了内置类 Promise,promise 的存在不仅仅是解决了 js 回调地狱的问题,而且按照 promise 的设定,promise 必定会返回一个结果。 这个结果要么是成功,要么是失败。这点其实就和刚刚提到的请求结果非常搭配,比如 axios 请求库就使用 promise 返回请求结果。所以针对上面的例子我们应该这样
// request 返回一个 promise request() .then(res => { // 这里处理请求成功 // ... // 如果业务代码可能出错,再使用 try catch try { // ... } catch (err) { // ... } }) .catch()
3. 错误处理只有一次
代码运行出错时,我们只处理一次。如果你能立即确定错误的处理方案,你就直接处理掉。 当你处理不了这个错误的时候,就要把详细的错误结果包装好。这样别人在调用的时候就能自己去处理错误了。
这里我们继续使用请求来举例,我们在请求时, 可能因为 token 过期导致请求 401 导致失败,你不可能在每个接口调用的地方都判断一次是否 401 吧。 这种 401 导致的失败是在封装请求方法的时候我们就能处理的,具体的请求不需要再去处理, 那我们就不需要把 401 这种错误告诉具体的请求使用函数,自己处理就行了,我们只需要告诉它错误了就行。
但是具体的请求业务错误可能就无法处理了。举个常见的例子,比如你获取一个列表数据,我们在请求时经常和后端约定请求成功码。经常看到有同学这样写:
request() .then(res => { // 假设请求成功码 0 为成功 if (res.code === 0) { // ... } else if (res.code === 1) { // ... // 参数错误 } else { // ... // 其他错误 } })
同样,非常不建议这样使用,为什么呢。因为第一点,业务错误也是错误。then 里面处理的是请求成功结果,按照刚刚说的,如果处理不了错误就交由具体使用方去处理,而在这里业务错误应该交由具体请求方的 catch 去处理。所以应该改成:
request() .then(res => { // then 里面只会请求成功,并且能够成功拿到数据 // ... }) .catch(err => { // 获取到包装好的错误信息 if (err.code === 1) { // ... // 参数错误 } else { // ... // 其他错误 } })
这样就能确保整个错误的传递路径的正确性,而不是不该你处理的错误你处理了。 具体在 封装 axios 的时候也有提到,就不展开了。
总结
我们先介绍了面向错误编程的概念,然后讲了 js 在处理错误时的一些方法,以及最后提到了错误处理该由谁去做的问题。ok,结束。
到此这篇关于详解JS中异常与错误处理的正确方法的文章就介绍到这了,更多相关JS异常处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!