什么情况下不会到达finally块?

Ree*_*erg 5 javascript node.js typescript

除了通常的嫌疑人(process.exit(),或进程终止/信号,或崩溃/硬件故障)之外,是否存在无法到达finally块中的代码的情况?

以下打字稿代码通常按预期执行(使用node.js),但有时会在第 4 行立即终止,并且不会引发任何异常,也不会更改进程退出代码(退出 0/成功):

1 import si from 'systeminformation';
2 async populateResolvedValue() {
3   try {
4     const osInfo = await si.osInfo();
5     ...
6   } finally {
7     console.log('whew!');  //  <=========== NOT REACHED!
8   }
9 }
Run Code Online (Sandbox Code Playgroud)

我已经在 IJ 调试会话中验证了这一点 -finally第 7 行的块有时不会执行,并且会在第 4 行立即终止(对堆栈进行一些展开)。我知道这种情况可能发生(并且仍然成功退出)的唯一情况是,如果在某处遇到段错误someAsyncFunc(),但我添加了“段错误处理程序”,但那里没有任何显示。

我也尝试过使用 Promise.then/finally 而不是 async/await 和 try/finally 语义 - 完全相同的行为。

Node.js:v12.18.2 和 v14.16.0

Ree*_*erg 6

感谢所有评论,我能够解决核心问题并找到满意的答案。

简而言之,是的 - 除了通常的嫌疑人之外,Promise.finally如果所讨论的承诺永远不会“解决”,那么(或带有等待的 try/finally)块可能永远不会到达并执行。有些 Promise 可能需要比预期更长的时间才能解决(I/O 等待等),并最终解决或拒绝,但如果 Promise从未解决(由于错误或设计),则依赖于该 Promise 的所有代码(thenfinally等)将永远不会解决执行。

那么什么是未解决的承诺,它们是如何发生的呢?如果承诺resolvereject回调函数从未被调用(由于错误或设计),则承诺可能永远不会解决。

考虑以下承诺:

 1 iPromiseTo(): Promise<any> {
 2   return new Promise((resolve, reject) => {
 3     if (this.willNeverHappen()) {
 4       resolve('I can forp');                    <====== Resolved
 5     } else if (this.willAlsoNeverHappen()) {
 6       reject('I cannot forp but I can rarp');   <====== Rejected
 7     } else {
 8       console.log('I cannot promise anything'); <====== Unsettled
 9     }
10   });
11 }
Run Code Online (Sandbox Code Playgroud)

如果 Promise 函数体在没有解析/拒绝的情况下完成(通过第 8 行到第 10 行),则任何依赖代码(包括稍后附加的 then/catch/finally 块(见下文))都将不会执行,并将被默默地忽略。在大多数情况下,这可能被认为是一个错误,这就是async函数在大多数情况下具有优势的原因。当函数体完成时,函数async总是会解析或拒绝(其函数体可能永远不会完成,但这是另一个主题)。

在语义中使用这个有缺陷的承诺Promise

 1
 2 youllBeSorry() {
 3   this.iPromiseTo()
 4     .then(promised => {
 5       console.log(promised); //  <=========== NOT REACHED!
 6     }).catch(error => {
 7       console.log(error);    //  <=========== NOT REACHED!
 8     }).finally(() =>
 9       console.log('whew!');  //  <=========== NOT REACHED!
10     });
11 }
Run Code Online (Sandbox Code Playgroud)

第 5、7 和 9 行将不会执行。

在 async/await 语义中使用它:

 1
 2 async youllBeSorry() {
 3   try {
 4     const promised = await this.iPromiseTo();
 5     ...                    //  <=========== NOT REACHED!
 6   } catch(error) {
 7     console.log(error);    //  <=========== NOT REACHED!
 8   } finally {
 9     console.log('whew!');  //  <=========== NOT REACHED!
10   }
11 }
Run Code Online (Sandbox Code Playgroud)

第 5、7 和 9 行将不会执行,该函数将在第 4 行静默返回,没有错误或任何后续异常处理。

另一类可能永远不会解决的承诺是那些由于无限循环或无限挂起等(由于错误或设计)而永远不会完成的函数体。这些实际上的行为与那些完成其函数体但从不调用resolveor的 Promise 类似reject,尽管两者之间可能存在其他差异,此处未涵盖。

好吧,那么我该如何处理这些“未解决”的承诺呢?不幸的是,没有规定的方法来捕获和处理承诺的这种特殊结果(在撰写本文时)。有一些建议,但我自己没有尝试过: await/async 如何处理未解决的承诺

await有可能已经可用或即将推出的 顶级(另请参阅其较旧的 IIFE 解决方法对应项): https://v8.dev/features/top-level-await

理想情况下,ECMAScript 将为异步行为提供更多顶级控制 - 请参阅 kotlin(甚至 python)协程,以获取有关如何更好地执行异步的更强大的示例。

finally最后(没有双关语),我要指出的是,在这种特殊情况下,该块通常按预期执行的原因是正在使用 ocif 框架,不幸的是,该框架exit在其 API 例程中嵌入了调用。在所有情况下,这里所讨论的承诺都可以实现/解决,但它与 ocif 中的退出调用竞争,因此并不总是得到解决,并且依赖代码并不总是在抛出退出异常和节点退出之前运行。So 被证明是这个特殊案例中的“常见嫌疑人”之一,但故障排除导致发现了 ECMAScript 中的这些缺陷。