Promises 和 Try/Catch 中的 Finally 终于……了?找到原因

2025-05-24

最后在 Promises 和 Try/Catch 中

finally...?

寻找原因

最近,我一直在尝试JavaScript 中的async/await关键字。我注意到,有时我很难将 Promises 的策略与新语法中需要的代码编写方式协调起来。最近,我在尝试finally一些try/catch代码块时,发现了一些意料之外的行为。

本文假设您对异步 JavaScript 代码的工作原理(尤其是 Promises 的工作原理)有大致的了解。(如果您正在寻找从回调到 async/await 关键字的异步 JS 的深入讲解,javascript.info上有一个相当不错的概述——您也可以查看Mostafa Gaafar 的文章,了解 async/await 的一些简洁特性。)

就上下文而言——在我投入大量时间的 JavaScript 代码库中,我们过去一直大量使用 Promises 来处理异步操作。总的来说,我更熟悉这种模式:

const loadSomething = () => {
  return fetchSomeData()
    .then(data => doSomethingWith(data))
    .catch(error => logAndReport(error))
}
Enter fullscreen mode Exit fullscreen mode

而下面这个就不那么为人熟知了:

const loadSomething = async () => {
  try {
    const data = await fetchSomeData()
    return doSomethingWith(data)
  } catch (error) {
    logAndReport(error)
  }
}
Enter fullscreen mode Exit fullscreen mode

finally...?

你会注意到,finally上面两个例子中都缺少回调/块。我在我的代码中很少使用它们,这导致我产生了误解(实际上,我对两者都有误解)。让我们深入探讨一下 Promises 和 try/catch 中这两个概念的区别!

finally在承诺中

当你使用该somePromise.then(x).catch(y).finally(z)模式时,你的业务逻辑通常发生在then回调函数中(上文 -解决x后你想做的事情)或在回调函数中(上文 - 返回你想在出现严重错误时传递的内容)。你可能从未在代码中使用过它——这没关系。somePromisecatchyfinally

根据MDN 文档finally回调允许你在 Promise 被解决(无论以何种方式解决或拒绝)后执行逻辑。它对Promise 最终解析的完全没有影响——它甚至无法访问该值。事实上,文档指出:

Promise.resolve(2).finally(() => {})将通过 来解决2

这意味着(有点违反直觉)您可以finally在整个承诺链中自由地添加回调,而无需改变它将解析的最终结果:

// Please don't do this 😅

Promise.resolve({ some: 'data' })
  .finally(() => { console.log('WHALE HELLO THERE 🐋') })
  .then(data => ({ ...data, anAdditional: 'key'  }))
  .finally(() => { console.log('Looks like we made it past the first step 🙏') })
  .then(data => ({ ...data, yetAnother: 'thing added' }))
  .finally(() => { console.log("We're done I think 🙌") })
  .then(data => {
    console.log('Final result:', data)
  })
Enter fullscreen mode Exit fullscreen mode

如果你运行此代码,你应该会看到以下内容:

执行上述代码的结果:每个步骤按顺序记录到控制台;最终结果是一个包含三个键的对象

finally在 try/catch 块中

try/catch/finally 模式在 JavaScript 中已经存在很长时间了——大约从 1.4 版(ES3 规范,大约在 1999 年)开始。我在这个模式和 Promise 的处理方式之间找到了一些逻辑上的相似之处:

try/ then
这就是我们的“快乐路径”逻辑 - 如果一切顺利,所有动作都会在这里发生!

catch
当事情出错时,这就是我们的最终归宿,并给我们一个自我救赎的机会🙏

finally
此逻辑将在try/ then(也可能是catch)逻辑完成后执行。无论是否遇到错误,此代码都会运行。

这里让我困惑的区别与语句有关return。如果你的finally代码块包含 return 语句,它对返回值没有影响finally。但是,如果你从代码块返回一个值,该值将覆盖所有其他返回值,并成为函数的最终结果。(查看文档中的这个示例!)

// This worked as I expected.
const returnFromTryCatch = (someFunction) => {
  try {
    return someFunction()
  } catch (error) {
    return `Caught an error: ${error}`
  } finally {
    // This block has no effect on the return value.
    console.log('All done!')
  }
}

// This was a surprise to me!
const returnFromFinally = (someFunction) => {
  try {
    return someFunction()
  } catch (error) {
    return `Caught an error: ${error}`
  } finally {
    // Wait... so I'm just swallowing my return and error handling?
    return 'All done!'
  }
}
Enter fullscreen mode Exit fullscreen mode

这很有道理,但我觉得前后矛盾。我使用 Promises 的经验让我想到——为什么一个finally可以覆盖函数的返回值?

一位女士正看着笔记本电脑陷入沉思的像素艺术

寻找原因

最后,我联系了我的技术主管,详细地讲述了我的烦恼,他给我发了一个相关的 StackOverflow 讨论的链接。查看了 ECMAScript 规范(重点是我加的)之后,我才明白了这个问题:

生产TryStatement : try Block Finally评估如下:

令 B 为 Block 的执行结果。
令 F 为 Finally 的执行结果。
如果 F.type 为 normal,则返回 B。
返回 F。

(值得注意的是,根据ECMAScript 规范, “完成类型”是“正常、中断、继续、返回或抛出之一” - 我假设不包含breakcontinuereturnthrow关键字的函数符合“正常”的条件。)

关于多个 Return 语句的注意事项

这篇文章中的代码示例没有使用单个 return 语句。我不想过多地讨论关于多个 return 语句的争论——我想说的是,总的来说,对于较长的函数,使用单个 return 语句过去对我来说很有效,但我发现在较短的块中,它的作用就没那么大了。不过,在这种情况下,使用单个 return 语句可能会让我的工作更轻松一些!

文章来源:https://dev.to/annarankin/finally-in-promises--trycatch-2c44
PREV
关于留下
NEXT
你对“这个”了解多少?