news 2026/10/6 7:55:16

JavaScript 中 `finally` 为何不可替代:对比 `try...catch` 之后的普通清理代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript 中 `finally` 为何不可替代:对比 `try...catch` 之后的普通清理代码
  • 文档/教程
  • 前端

【免费下载链接】en.javascript.info

Modern JavaScript Tutorial

项目地址:https://gitcode.com/gh_mirrors/en/en.javascript.info
点击查看免费下载

导读

在 JavaScript 的异常处理中,try...catch之后既可以追加一个finally子句,也可以直接写一段"收尾"代码,二者在无跳转时行为完全一致。本文以 Modern JavaScript Tutorial(本仓库 1-js/10-error-handling/1-try-catch 章节)中的经典辨析题为线索,深入对比这两种写法,揭示finally在函数内部发生return或throw等"跳转退出"时的关键语义差异,并通过可运行的示例代码,帮助读者掌握何时必须使用finally来保证清理逻辑的确定性执行。

题目回顾:两段看似等价的清理代码

原题(1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md)给出了两个代码片段,要求判断它们是否完全等价:

片段一:使用finally执行清理

try { // work work } catch (err) { // handle errors } finally { // cleanup the working space }

片段二:把清理代码直接放在try...catch之后

try { // work work } catch (err) { // handle errors } // cleanup the working space

需求很明确:无论是否发生错误,清理动作都必须执行。仅从这个需求看,两种写法似乎都能满足——如果try块正常结束,两段清理代码都会运行;如果抛出了错误并被catch捕获,两段清理代码也都会在catch结束后运行。

那么,这两段代码真的等价吗?答案是否定的,而差异恰恰隐藏在"函数内部 + 跳转退出"这一组合场景中。官方解答见 1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md。

核心差异:finally能覆盖"跳转退出"

官方解答给出的结论是:当代码位于函数内部,并且存在"跳出try...catch"的行为时,两种写法产生明显差异。这里的"跳出"包含两种常见情形:

  1. try块或catch块中的return语句;
  2. catch块中无法处理错误时执行的throw(重新抛出)。

finally子句对try...catch的任何退出路径都生效——包括return和throw,它在try...catch结束之后、控制权交还给调用方之前运行。而"放在函数末尾的普通代码"在这些跳转场景下根本不会执行。

场景一:return跳出try...catch

以下代码来自官方解答:try块中执行了return "result",但finally中的清理仍然会运行:

function f() { try { alert('start'); return "result"; } catch (err) { // ... } finally { alert('cleanup!'); } } f(); // 输出: start, 然后 cleanup!

执行流程是:try块执行alert('start'),遇到return时不会立即把控制权交还给调用方,而是先执行finally中的alert('cleanup!'),之后函数才真正返回。如果你把cleanup挪到函数末尾,return会直接跳过它,清理逻辑永远不会执行。

场景二:throw重新抛出错误

再看catch块中重新抛出错误的场景:catch捕获了错误,但判断自己无法处理,于是执行throw err。此时控制流同样会"跳出"try...catch,而finally依然会在错误继续向外传播之前执行:

function f() { try { alert('start'); throw new Error("an error"); } catch (err) { // ... if ("can't handle the error") { throw err; } } finally { alert('cleanup!') } } f(); // 输出: start, 然后 cleanup!,最后错误向外抛出

官方解答明确指出:"正是finally在这里保证了清理的执行。如果只是把代码放在函数f的末尾,在这些场景下它根本不会运行。"

finally的完整语义:任何退出路径都执行

语法与执行路径

finally是try...catch的可选第三个子句,完整语法如下(见 article.md 的 "try...catch...finally" 一节):

try { // 尝试执行代码 } catch (err) { // 处理错误 } finally { // 无论何种结果,总是执行 }

只要finally存在,它就在所有情况下运行:

  • try块无错误时,在try之后执行;
  • try块有错误时,在catch之后执行。

下图展示了try...catch的两条基本执行路径(图片来源:try-catch-flow.svg):

交互式验证

主文档中提供了一个可直接在浏览器运行的验证示例:弹窗询问"是否制造一个错误?",回答 "Yes" 时执行顺序为try -> catch -> finally,回答 "No" 时为try -> finally:

try { alert('try'); if (confirm('Make an error?')) BAD_CODE(); } catch (err) { alert('catch'); } finally { alert('finally'); }

无论哪条路径,finally都必然出现——这正是它作为"无论如何都要收尾"这一承诺的实现基础。

实战用例:用finally保证耗时测量不遗漏

finally最常见的应用场景是:开始一项需要收尾的工作(如计时、关闭资源),无论成功或失败都必须完成收尾。主文档给出了测量fib(n)执行时间的完整示例:

let num = +prompt("Enter a positive integer number?", 35); let diff, result; function fib(n) { if (n < 0 || Math.trunc(n) != n) { throw new Error("Must not be negative, and also an integer."); } return n <= 1 ? n : fib(n - 1) + fib(n - 2); } let start = Date.now(); try { result = fib(num); } catch (err) { result = 0; } finally { diff = Date.now() - start; } alert(result || "error occurred"); alert(`execution took ${diff}ms`);

这里fib对负数或非整数输入会抛出错误。通过把diff = Date.now() - start放进finally,无论fib成功返回还是抛出异常,耗时都能被正确记录:

  • 输入35:正常执行,finally在try之后运行,输出真实耗时;
  • 输入-1:立即抛错,catch将result置为 0,finally仍会运行,耗时记录为约0ms。

主文档对此的总结是:函数无论是通过return还是throw结束,finally都会执行。

变量作用域的注意点

上面的例子中,result和diff都声明在try...catch之前,这是有意为之:如果在try块内用let声明,变量只在其内部可见,finally和catch都将无法访问。涉及多个块共享数据时,务必把声明提升到try...catch...finally之外。

finally与return、throw的细节语义

finally在return之前的执行时机

主文档用func()演示了return与finally的先后关系:finally在控制权返回调用方之前执行,因此调用方看到的效果是"先弹出finally的 alert,再得到返回值":

function func() { try { return 1; } catch (err) { /* ... */ } finally { alert('finally'); } } alert(func()); // 先执行 finally 中的 alert,再执行本行

不带catch的try...finally

当"不想在这里处理错误(让错误继续向上传播),但必须保证已启动的工作被收尾"时,可以省略catch,只写try...finally:

function func() { // 开始一件需要收尾的事情(如计时) try { // ... } finally { // 即使一切崩溃,也要完成收尾 } }

在这种写法中,try块内的错误因为没有catch而必然向外抛出,但finally会在执行流离开函数前先行运行。这与前文throw场景的结论相互印证:finally覆盖的是"任何退出",包括异常路径。

结论与判断准则

回到题目本身:两段代码并不等价。判断依据如下:

场景finally清理紧随其后的普通清理代码
try正常结束✅ 执行✅ 执行
catch正常结束✅ 执行✅ 执行
try/catch中return跳出✅ 执行❌ 被跳过
catch中throw重新抛出✅ 执行❌ 被跳过
外层嵌套try...catch捕获场景✅ 执行❌ 被跳过

实用准则:当清理代码与try...catch处于同一函数,且函数内存在return或重新抛错(throw err)的可能性时,请使用finally而不是"事后代码";当清理代码位于顶层(不在函数内)且不存在跳转路径时,两种写法行为一致,但使用finally依然能提高代码意图的清晰度,让"无论如何都要执行"的语义一目了然。

延伸阅读

  • 本章主文档:1-js/10-error-handling/1-try-catch/article.md(含try...catch基础语法、错误对象、throw、重新抛出与全局捕获window.onerror的完整讲解)
  • 本题目原文:task.md
  • 官方解答:solution.md
  • 流程示意图:try-catch-flow.svg
  • 下一章(自定义错误类型):2-custom-errors
  • 文档/教程
  • 前端

【免费下载链接】en.javascript.info

Modern JavaScript Tutorial

项目地址:https://gitcode.com/gh_mirrors/en/en.javascript.info
点击查看免费下载

相关推荐

上一篇:深入理解 ESLint 扩展体系:插件、共享配置、自定义格式化器与解析器完全指南
下一篇:Karakeep 在 Unraid 上的完整部署指南:Docker Compose Manager 与 Community Apps 双路径实战

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 7:54:43

【2027大数据项目】基于大数据的奥运会奖牌榜数据分析与可视化,附源码_高质量项目_可视化_数据分析_毕设选题推荐_SPark_Hadoop_毕设指导

&#x1f496;&#x1f496;作者&#xff1a;计算机毕业设计杰瑞 &#x1f499;&#x1f499;个人简介&#xff1a;曾长期从事计算机专业培训教学&#xff0c;本人也热爱上课教学&#xff0c;语言擅长Java、微信小程序、Python、Golang、安卓Android等&#xff0c;开发项目包括…

作者头像 李华
网站建设 2026/10/6 7:47:28

The Generative Energy Arena (GEA): Incorporating Energy Awareness in Large Language Model (LLM) H...

文章主要内容总结 本文针对大型语言模型(LLMs)评估中能源消耗因素被忽视的问题,提出了生成能源竞技场(Generative Energy Arena, GEA) 这一评估平台。 背景:现有LLM评估方法存在局限——自动化基准测试与人类偏好相关性低,传统人类评估可扩展性差,公开竞技场(如LM Ar…

作者头像 李华