天天看点

mfc try catch 捕获并显示_② - throw、try、catch、finally - JS异常处理

个人学习笔记,对基础知识的整理和巩固。

JavaScript提供了捕获和处理异常的功能。

类似以下操作均会触发错误:

// ①
           

当发生错误时,代码将停止运行,同时抛出一个错误对象,它是Error类的一个实例。

我们会在控制台看到它:

mfc try catch 捕获并显示_② - throw、try、catch、finally - JS异常处理

但更多情况下,我们会看到更具体的错误,它们总共有六类,包括EvalError、InternalError等等(更多:Error类型),它们都是Error的子类,分别表示不同的错误原因。

除此之外,我们自己也可以主动触发错误:

▉ throw[1]

首先,我们需要创建一个错误对象,只需要把错误信息传递给Error(或者具体的EvalError等)就可以了:

let 
           

现在我们得到了一个错误对象,接下来只要使用throw语句把它抛出:

throw 
           

控制台立刻输出了这个错误:

mfc try catch 捕获并显示_② - throw、try、catch、finally - JS异常处理

这样,我们就主动触发了一个自己创建的错误,执行到这里的时候,代码的运行将中断。

事实上,throw可以抛出的内容不仅仅是Error对象,数字、字符串甚至是对象、函数等等都可以。

throw 
           

控制台都会如实输出:

mfc try catch 捕获并显示_② - throw、try、catch、finally - JS异常处理

不过大多数时候,只是中断执行并且抛出和输出错误并不是我们的目的,或者甚至说是我们想要避免的。

我们希望代码即使出现错误的时候也能继续运行而不至于影响大局,我们希望能够对可预见的错误做出相应的处理。

所以我们需要异常(错误)处理:

▉ try、catch[2]

try和catch就是用来处理错误的工具,在“try块”里我们运行可能会出错的代码,然后当出错的时候用catch来捕获并作出相应处理。

try
           

这里的错误可能是执行出错(被动抛出),也可能是我们用throw主动抛出错误,前者会抛出一个Error(也可能是子类如EvalError)的实例对象,后者可能是throw出的任何值。

任何错误的抛出,try块里的代码都会立刻中断,同时将它传递到catch块,并接着运行catch块代码,之后,如果catch块内没有发生错误的话,将继续往下执行。

catch后面的括号用来接收捕获到的错误,可用作判断处理。

被捕获(catch)的错误不会输出到控制台(我们很容易可以注意到前面控制台输出的错误都以“Uncaught”开头)。

如果try块内没有抛出错误,catch块将不会执行。

▉ 异常处理的嵌套

既然try块内可以像其他地方一样执行代码,那么当然也可以在try块(或者catch块)里再写一个try和catch的组合:

try 
           

在这个示例里,次级try块内抛出1,被其相应的catch捕获,然而这个catch块并未对这个错误做任何处理,而是把它又一次抛了出来,因为它运行在上级的try块里,所以这个错误又再一次被上级try对应的catch捕获,从而输出1。

所以我们知道:

  • try或catch里可以嵌套try...catch
  • catch块内仍然可能发生错误
  • 错误会逐级向上抛出,直到被捕捉,到最顶层而未被捕捉的错误将被输出到控制台
  • 错误总是被包裹它的最内层的try所对应的catch捕获

▉ finally

除了catch之外,还可以加上finally语句块。

try
           

不管try块内有没有抛出错误,finally语句块总会被执行。

如果try块内发生错误,finally块将在catch块之后被执行;如果没有发生错误,将跳过catch块,直接运行finally块。

我们其实还可以不写catch块(try块后必须至少跟一个catch或finally,不能只写try):

try 
           

假如不写catch块,try块内抛出错误之后,会先执行finally块,之后,因为错误并未被捕捉,所以将继续向外层抛出(抛出错误即意味着原位置运行中断)。

如果finally块内发生错误会怎样呢?

■ 错误的覆盖

不管try块和catch块内有没有抛出错误,如果finally块内抛出错误,外层代码只能接收到finally块内抛出的错误。

try 
           

在上面这个例子里,内层try块内(位置①)抛出的错误1被捕获,接着其catch块内(位置②)又抛出了一个错误2,之后将立刻运行finally块,finally块内(位置③)竟然又抛出了一个错误3,这个错误替代了catch块内抛出的错误,所以这一组try{}catch{}finally{}最终抛出错误3。它们处在外层try块内,所以这个错误被外层try对应的catch捕捉,并被最终输出:3。

另外一个特殊情形是当运行在函数里的时候,如果finally返回了一个值,try或catch块内抛出的错误将同样被忽略:

function 
           

上面这个例子里,catch块内抛出了一个错误,在一般情况下,如果finally块内没有返回值,错误将在执行完finally块后被层层抛出,直到被输出到控制台(没有被捕捉),而函数的运行将中断,所以也不会输出任何值。但是finally块内返回了一个值,所以catch块内的错误被忽略了,返回值3被正常输出,控制台看不到任何错误信息。

▉ 总结

  • 任何错误的抛出都会导致程序在原位置的运行立刻停止。
  • try、catch、finally块内都可以再嵌套try、catch、finally的组合。
  • 当错误被抛出时,程序会在该位置立刻停止运行,然后层层上溯,直到发现自己被包裹在一个try块里,而把该错误传递到其相应的catch块,然后在此接着运行,如果其没有对应的catch块,将在运行完finally块后继续上溯,直到被捕捉或到达代码顶端而被输出到控制台。
  • finally块内的返回值或抛出的错误将覆盖任何原有的错误。

▉ 应用

我们已经有了在JavaScript中处理异常的办法,那么,应该怎样使用呢?

■ 首先想到的用法:流程控制(应当禁止)

说实话,当我看到throw与catch默契的关系的时候,第一时间不是想到用来处理错误,而是用它来进行流程控制,“多么完美啊!”,我只需要这样写:

try 
           

好像还是很美的,我甚至还考虑到假如try块真的出现错误:只要把它抛出。

可事实是,即使有这种需求,就算使用匿名函数也并不比它麻烦多少,甚至或许还更简单:

switch 
           

尤其它不会打乱原有的错误处理逻辑。

■ 最暴力直接的用法(不推荐)

看来try...catch最好还是用来处理错误,但是显然,它不是用来在开发阶段处理bug的工具,因为我们有更好的debugger和开发者工具。

事实上,回想我不长的代码生涯,我都是这样用的:

有些时候,我们想运行一段代码,但是我们知道这段代码很可能会出错,而我又不希望它影响接下来的步骤,也就是说,我想运行一段代码,即使失败了也没事儿。如此,这样写显然是最简单的:

try
           

也许你会发现这样写也能运行:

try
           

能省则省嘛,但是它在IE上会报错,可能因为它并不符合标准,所以尽量还是写上吧。另外,参数是不能省略的,即使是在chrome上。

这样写也不行:

try
           

因为没有catch,错误没有捕捉,仍旧会往上抛。

用这种方法来运行“一段无关紧要的代码”时,应该确定该内容

真的无关紧要

,以至于它发生任何错误都可以忽视它。

所以,也许我们应该再保险一点,虽然它无关紧要,但还是把这个错误记录下来:

try
           

也可以用console.error或者console.log,更多可参阅 Console - MDN 。

即便如此,这样做似乎仍然显得不那么好,我们也许应该对错误做出更精确的判断和处理。

■ 基本用法的思考

我已经知道“错误处理”就应当用来处理错误,但是,错误不应该是在开发阶段就处理掉的吗?我们应该考虑到可能遇到的各种情形,尽量不出错误。

可现实是,各种环境等因素总是在不停地发生变化,而我们的代码不是总能预知,我们确实有暂时容忍错误存在的需求,或者有时候,容忍错误存在在一定程度上是简化我们代码的一种方法(应该适度),再或者说,即使部分功能出现错误,但是其他功能还是得继续运行。

总而言之,错误处理是我们把错误考虑进程序的一种方法。

错误具体应该怎样处理呢?

我们可以首先把错误分为两种:

  • 已预知的错误(已知错误)
  • 未预料的错误(未知错误)

然后可以按照意义分类:

  • 程序可以处理的
  • 程序不能处理的

这两种分类不完全重合,但存在相当的对应关系。

而针对错误,抛出与不抛出的区别在于:

  • 抛出错误表示程序必须中断
  • 不抛出错误表示程序还可以运行

具体应该采取什么样的做法要视情况而定,比如程序的重要程序、精密程度,程序之间的相关性,当然,还有具体的逻辑关系。

大多数时候,我觉得应该秉承这样的原则:

  1. 抛出不能处理的错误。
  2. 通知未知的错误(包括抛出、输出、记录等)。
  3. 程序应当中断时就中断。

基于这样的原则,我们可以根据实际情况选择抛出部分或者不抛出。

下面是一些示例(非范例或实例):

■ 创建自己的错误

一般情况下,前端代码复杂度应该不会很高,我觉得没有必要抛出自定义的错误或其他值,而用流程控制和函数返回值等等代替,因为感觉会在某种程度上增大错误处理的复杂度。但是现在JS也能写后端了,而且即使是前端,可能工程性较强或者对错误处理有特殊需求的时候也需要用到。

这个时候我们可以通过继承Error来创建自己的错误:

class 
           

Error对象有三个比较重要的属性:

  • name 表示错误的类型,如内置错误类型"TypeError",或者我们自定义的"MyError"。
  • message 表示错误信息,如“Cannot read property '***' of undefined”。
  • stack 是错误的追踪堆栈信息,创建Error对象的时候会自动创建。

可以多重继承,对自定义的错误进行分类:

// 以下 我尝试写了一个自定义错误的继承机制
           

以上按照自己的理解和思考,看了一些别人写的,大概写了这么个东西出来,以后学到更多东西肯定还会对很多东西再改动,可能会再回来改。

■ 异步编程下的错误处理

try{}catch{}只能捕获同步编程下的错误,对于异步编程时可能比较麻烦。

倒是看了一些NodeJS错误处理的文章,但是这一块等以后补吧,等再巩固了Promise异步编程啥的知识,了解了一些后端JS的编程之后。

■ 更多

可能忘了还有一些其他东西没写,但是好累啊。。。

还是得换种写法,感觉自己像唠叨的老太太,同一件事情反复说,还是得精简一点,写成这样没有意义,浪费时间,给自己看就够了,不能老觉得是给别人看的。

下次的宗旨:

  • 都覆盖到
  • 道理清晰,看得懂
  • 文字精炼,一目了然
  • 写得快,学习占大头,整理占小头

另外感觉就算写这个也还是会忘,可能记性不太好。。。弄完之后还是得尽快进入到实战,再回头翻,再熟练。

参考

  1. ^throw - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/throw
  2. ^try...catch - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/try...catch