try {
// may throw three types of exceptions
willThrowError()
} catch (e if e instanceof TypeError) {
// statements to handle TypeError exceptions
} catch (e if e instanceof RangeError) {
// statements to handle RangeError exceptions
} catch (e if e instanceof EvalError) {
// statements to handle EvalError exceptions
} catch (e) {
// statements to handle any unspecified exceptions
}
那么符合 ECMAScript 标准的「条件 catch 子句」应该这样写:
try {
// may throw three types of exceptions
willThrowError()
} catch (e) {
if (e instanceof TypeError) {
// statements to handle TypeError exceptions
} else if (e instanceof RangeError) {
// statements to handle RangeError exceptions
} else if (e instanceof EvalError) {
// statements to handle EvalError exceptions
} else {
// statements to handle any unspecified exceptions
}
}
<!DOCTYPE html>
<html>
<body>
<video id="video" controls src="https://dl.ifanr.cn/hydrogen/landing-page/ifanr-products-introduce-v1.1.mp4"></video>
<script>
window.onload = function () {
const video = document.querySelector('#video')
video.play() // Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.
}
</script>
</body>
</html>
写了那么久的 JavaScript,似乎真的没有很认真地去了解
try...catch...finally
的各种用法,真是惭愧了!Anyway,不懂就学...一、错误与异常
错误,在程序中是很常见的。它可以是 JS 引擎在执行代码时内部抛出的,也可以是代码开发人员针对一些不合法的输入而主动抛出的,或者是网络断开连接导致的错误等等...
可能很多人会认为,「错误」和「异常」是同一回事,其实不然,一个错误对象只有在被抛出时才成为异常。
1.1 错误
在 JavaScript 中,错误通常是指 Error 实例对象或 Error 的派生类实例对象(比如
TypeError
、ReferenceError
、SyntaxError
等等)。创建Error
实例对象很简单,如下:虽然
Error
及其派生类是构造函数,但是当作函数调用也是允许的(即省略new
关键字),同样会返回一个错误实例对象。一个错误实例对象,包含以下属性和方法:
我们写个最简单的示例,打印看下各大浏览器的情况:
插个题外话:
不知道有人没有对此有疑惑的,为什么
console.log()
一个Error
对象,打印出来的是字符串,而不是一个对象呢?那么,如果想打印出
Error
对象,使用console.dir()
即可。前面
console.log()
打印结果为字符串的原因其实很简单,那就是console.log()
内部「偷偷地」做了一件事,当传入的实参为Error
对象(或其派生类错误对象),它会先调用Error
对象的Error.prototype.toString()
方法,然后将其结果输出到控制台,所以我们看到的打印结果为字符串。其实现如下:
细心的同学会发现,在不同浏览器下,其打印结果可能会不相同(但不重要)。原因也非常简单,
console
并不是 ECMAScript 标准,而是浏览器 BOM 对象提供的一个接口,其标准由 WHATWG 机构制定,虽然标准是统一的,但实现的是各浏览器厂商大爷们,它们有可能不会严格遵守规范去实现,因而产生差异化。比如,此前写过一篇文章是关于不同宿主环境下 async/await 和 promise 执行顺序的差异,就因为 JS 引擎实现差异导致的。1.2 异常
前面提到,当错误被抛出时就会成为异常。
假设我们编写的代码存在语法错误,那么在编译阶段的语法分析过程就会被聪明的 JS 引擎发现,因而在编译阶段便会抛出 SyntaxError。
假设我们代码没有语法错误,但错误地引用了一个不存在的变量,那么在执行阶段的执行上下文过程(代码执行之前的一个过程),聪明的 JS 引擎发现在其作用域链上找不到该变量,那么就会抛出 ReferenceError。
假设即不存在语法错误,也没有引用错误,但我们对一个变量做了“不合法”的操作,比如
null.name
、'str'.push('ing')
,那么 JS 引擎就会抛出 TypeError。还有很多很多,就不举例了。
前面都是 JS 引擎主动抛出的错误,那么,我们开发者则可通过
throw
关键字来抛出错误,语法很简单:请注意,在 JavaScript 中
throw
关键字和return
、break
、continue
等关键字一样,会受到 ASI(Automatic Semicolon Insertion)规则的影响,它不能在throw
与expression
之间插入任意换行符,否则可能得不到预期结果。语法很简单,但通常项目中「不建议」直接抛出一个字面量,而是抛出
Error
对象或其派生类对象,应该这样:原因是
Error
对象会记录引发此错误的文件的路径、行号、列号等信息,这应该是排除错误最有效的信息。在 ESLint 中的 no-throw-literal 规则,正是用来约束上述直接抛出字面量的写法的。除了
throw
关键字之外,ES6 中强大的 Generator 函数也提供了一个可抛出异常的方法:Generator.prototype.throw()
。它可以在函数体外抛出异常,然后在函数体内捕获异常。打印结果是
inner --> Error: oops
。如果生成器函数体内没有try...catch
去捕获异常,那么它所抛出的异常可以被外部的try...catch
语句捕获到。当生成器「未开始执行之前」或者「执行结束之后」,调用生成器的
throw()
方法。它的异常只会被生成器函数外部的try...catch
捕获到。若外部没有try...catch
语句,则会报错且代码就会停止执行。详看二、try...catch
对于可能存在异常的代码,我们通常会使用
try...catch...finally
去处理一些可预见或不可预见的错误。语法有以下三种形式:且必须至少存在一个
catch
块或finally
块。以上这些语法,写过 JavaScript 相信都懂。
曾经 Firefox 59 及以下版本的浏览器,有一种 Conditional catch-blocks 的「条件
catch
子句」的语法(请注意,其他浏览器并不支持该语法,即便是远古神器 IE5,因此知道有这回事就行了)。它的语法如下:那么符合 ECMAScript 标准的「条件
catch
子句」应该这样写:请注意,
try...catch
只能以「同步」的形式处理异常,因此对于 XHR、Fetch API、Promise 等异步处理是无法捕获其错误的,究其原因就是 Event Loop 嘛。当然实际中可能结合async/await
来控制会更多一些。2.1 catch子句
我们知道,若
try
块中抛出异常时,会立即转至catch
子句执行。若try
块中没有异常抛出,会跳过catch
子句。其中
exception_var
表示异常标识符(如catch(e)
中的e
),它是「可选」的,因此可以这样编写try { ... } catch { ... }
。通过该标识符我们可以获取关于被抛出异常的信息。2.2 finally 子句
而
finally
子句在try
块和catch
块之后执行,但在下一个try
声明之前执行。无论是否异常抛出,finally
子句总是会执行。对于这个我表示很无语,可能整个前端圈子就我还不知道吧,原来
finally
还能return
一个值,在做项目的过程中,确实没写过和见过在finally
中return
某个值的,让您见笑了,实在惭愧。但请注意,若要在
try...catch...finally
中使用return
,它只能在函数中运行,否则是不允许的,会抛出语法错误。2.3 执行顺序
在平常的项目中,一般的
try...catch
写法是在try
块中return
,catch
块则作相应的异常处理,少数情况也会在catch
块中return
。因此,大家对这种常规写法的执行顺序应该没什么问题。先来个谁都会的示例:
接着再看,它打印什么,函数又返回什么呢?
前面提到,如果
finally
块中含有return
语句,那么它的return
值将作为当前函数的返回值,因此foo()
结果为"complete"
。然后我们再稍微改动一下,在
try
块中return
一个值,看下结果又有什么不同?由于
try
块中没有抛出异常,因此catch
块会被跳过,不执行,但是finally
块还是会执行的,而且它里面返回了"complete"
,因此这个值也就作为foo
函数的返回值了。但是,这就完了吗?
还没有,我们再看一个示例,看看里面这个
bar()
函数是惰性求值?还是怎样?以上示例,打印顺序和结果是什么呢?
假设
catch
块中的return bar()
换成throw bar()
呢,结果又有什么变化呢?如果换成这个你就犹豫了,说明你理解得不够深刻,因此这里我不给出答案,你自己去试试,效果更佳!综上所述,
finally
块的执行时机如下:2.4 嵌套使用
它是可以嵌套使用的,当内部的
try...catch...finally
中抛出异常,它会被离它最近的catch
块捕获到。注意,本节内容所述都是同步代码,而不存在任何异步代码。
到此,已彻底弄懂
try...catch...finally
语句了,再也不慌了!三、异常有哪些?
在 Web 中,主要有以下几种异常类型:
3.1 JavaScript 异常
try...catch
可以捕获同步任务导致的异常,也可以捕获async/await
中的异常。Promise
中抛出的异常,则可通过Promise.prototype.catch()
或Promise.prototype.then(onResolved, onRejected)
捕获。3.2 DOM Exception
在调用 DOM API 时发生的,都属于 DOM Exception。比如:
未完待续...