HOWTO · JavaScript

在 JavaScript 中使用不带 Catch 的 try 语句

在今天的文章中,我们将学习在 JavaScript 中使用不带 Catch 的 try 语句。

本页内容

可以——只要存在 finally 子句,JavaScript 的 try 语句就可以省略 catch。本教程将解释在什么情况下不带 catch 的 try 是合法的、抛出的错误会发生什么,以及什么时候适合使用这种形式。

在 JavaScript 中 try 如何在没有 catch 的情况下工作

JavaScript 的 try 语句用于包裹可能抛出错误的代码,它在任何允许语句出现的位置都是合法的:脚本的顶层、函数内部,或者嵌套在另一个 try 中。它唯一不能做的是单独存在——try 块后面必须跟着 catch 子句、finally 子句,或者两者都有,因此两者都没有的裸 try 是语法错误。大括号 {} 始终是必需的,即使只有一条语句也是如此。

语法:

下面语法骨架中的占位符名称和注释保持英文原样。

本节中的代码以及显示的标签或输出仍为英文。

try {
  // try_statements: code that may throw
} catch (exception_var) {
  // catch_statements: handle the thrown value
} finally {
  // finally_statements: always run afterward
}
  1. try_statements 是要执行的语句。
  2. catch_statements 是当 try 块中抛出值时执行的语句。
  3. exception_var 是一个可选标识符,用于保存关联 catch 块接收到的抛出值。
  4. finally_statements 在 try 语句完成后运行,无论是否抛出或捕获了值。

因为 catch 只有在存在 finally 时才可以省略(反之亦然),所以语法恰好允许三种组合:

  1. try...catch
  2. try...finally
  3. try...catch...finally

当存在 catch 块且 try 块内的语句(或它调用的函数)抛出异常时,控制流会立即转移到 catch;当没有抛出任何东西时,catch 会被完全跳过。finally 块总是在 try 和 catch 块执行完毕后运行,无论是否抛出了值,也无论 catch 是否处理了它。

在没有 catch 子句的情况下,finally 仍然会先运行,然后被抛出的值继续向调用者传播。嵌套时也是如此:try 语句可以嵌套,当内层 try 语句没有 catch 时,由外层 try 的 catch 块来处理该错误。

完整的语法请参阅 MDN 文档中的 try...catch。

示例:不带 catch 的 try...finally

下面的代码段把一个自身没有 catch 的 try...finally 放在一个外层 try...catch 内,这样我们就可以观察 finally 块的运行,并看到被抛出的值最终去了哪里。0 / 0 的求值结果是 NaN,所以 throw new Error(0 / 0) 抛出了一个消息为 NaN 的 Error。

运行这个示例:

本节中的代码以及显示的标签或输出仍为英文。

try {
  try {
    console.log('Executing try block')
    throw new Error(0 / 0)
  } finally {
    console.log('Final Call')
  }
} catch (error) {
  console.log('Outer catch received: ' + error.message)
}
Executing try block
Final Call
Outer catch received: NaN

内层 try 先打印它的日志行,然后抛出异常。因为它没有 catch,控制流直接进入 finally,由 finally 打印出 Final Call。只有在 finally 完成之后,Error 才继续传播,由外层 catch 接收并打印它的 NaN 消息。如果没有那个外层的 catch,同样的错误会传播给调用者;在顶层,它会在打印 Final Call 之后以未捕获错误的形式暴露出来。

什么时候使用不带 catch 的 try

当一段代码需要保证清理工作、而错误本身应该交给调用者处理时,就该使用 try...finally。finally 的典型工作包括释放资源、关闭连接或文件句柄、恢复共享状态(例如重新启用按钮或重置标志),以及停止定时器——无论 try 块是正常结束还是抛出异常,这些工作都必须执行。

当代码对错误没有什么有意义的事情可做时,这种形式就是正确的选择:原封不动地重新抛出只会增加噪音,而空的 catch 块则会静默吞掉所有失败,让 bug 在远离其根源的地方才暴露出来。try...finally 既保留了清理保证,又让调用者能够看到并处理错误。如果函数确实需要检查抛出的值、记录日志或进行恢复,那就应该添加一个真正的 catch 子句。

不带 catch 的 try 的局限与陷阱

  • finally 永远不会处理或抑制错误。finally 结束后,未被处理的抛出值会继续向调用者传播;在顶层,它会终止脚本或报告为未捕获错误。
  • 你无法观察被抛出的值。没有 catch,这个结构就无法访问抛出的内容,因此不能记录错误消息,也不能根据错误类型分支。
  • finally 内部的控制流会覆盖任何待定的操作。在 finally 中使用 return 或 throw 会丢弃仍在传播的值——包括抛出的错误,它随后会无声无息地消失——所以应让 finally 只做简单的清理工作,既不返回也不抛出。
  • try...finally 之后的语句只有在没有抛出任何东西时才会运行,并且 finally 本身也不能依赖 try 块成功执行,因为它同样会在失败路径上运行。