最后在 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))
}
而下面这个就不那么为人熟知了:
const loadSomething = async () => {
try {
const data = await fetchSomeData()
return doSomethingWith(data)
} catch (error) {
logAndReport(error)
}
}
finally
...?
你会注意到,finally
上面两个例子中都缺少回调/块。我在我的代码中很少使用它们,这导致我产生了误解(实际上,我对两者都有误解)。让我们深入探讨一下 Promises 和 try/catch 中这两个概念的区别!
finally
在承诺中
当你使用该somePromise.then(x).catch(y).finally(z)
模式时,你的业务逻辑通常发生在then
回调函数中(上文 -解决x
后你想做的事情)或在回调函数中(上文 - 返回你想在出现严重错误时传递的内容)。你可能从未在代码中使用过它——这没关系。somePromise
catch
y
finally
根据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)
})
如果你运行此代码,你应该会看到以下内容:
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!'
}
}
这很有道理,但我觉得前后矛盾。我使用 Promises 的经验让我想到——为什么一个finally
块可以覆盖函数的返回值?
寻找原因
最后,我联系了我的技术主管,详细地讲述了我的烦恼,他给我发了一个相关的 StackOverflow 讨论的链接。查看了 ECMAScript 规范(重点是我加的)之后,我才明白了这个问题:
生产
TryStatement : try Block Finally
评估如下:令 B 为 Block 的执行结果。
令 F 为 Finally 的执行结果。
如果 F.type 为 normal,则返回 B。
返回 F。
(值得注意的是,根据ECMAScript 规范, “完成类型”是“正常、中断、继续、返回或抛出之一” - 我假设不包含break
、continue
、return
或throw
关键字的函数符合“正常”的条件。)
关于多个 Return 语句的注意事项
这篇文章中的代码示例没有使用单个 return 语句。我不想过多地讨论关于多个 return 语句的争论——我想说的是,总的来说,对于较长的函数,使用单个 return 语句过去对我来说很有效,但我发现在较短的块中,它的作用就没那么大了。不过,在这种情况下,使用单个 return 语句可能会让我的工作更轻松一些!
文章来源:https://dev.to/annarankin/finally-in-promises--trycatch-2c44