【问题标题】:Node logging unexpected UnhandledPromiseRejectionWarning节点记录意外的 UnhandledPromiseRejectionWarning
【发布时间】:2022-01-20 09:31:40
【问题描述】:

我有一段代码导致 Node 记录 UnhandledPromiseRejectionWarning。但我不确定为什么。这是归结的代码:

export class Hello {
  async good(): Promise<string> {
    let errorP = this.throwError();
    let responseP = this.doSomething();
    let [error, response] = await Promise.all([errorP, responseP]);
    return response + '123';
  }

  async bad(): Promise<string> {
    let errorP = this.throwError();
    let responseP = this.doSomething();
    let response = (await responseP) + '123';
    let error = await errorP;
    return response;
  }

  private async throwError(): Promise<string> {
    await (new Promise(resolve => setTimeout(resolve, 1000)));
    throw new Error('error');
  }

  private async doSomething(): Promise<string> {
    await (new Promise(resolve => setTimeout(resolve, 1000)));
    return 'something';
  }
}

调用 try { await hello.bad(); } catch (err) {} 会导致节点记录 UnhandledPromiseRejectionWarning

调用 try { await hello.good(); } catch (err) {} 不会记录警告

完全错误:

(node:25960) UnhandledPromiseRejectionWarning: Error: error
    at Hello.<anonymous> (C:\hello-service.ts:19:11)
    at Generator.next (<anonymous>)
    at fulfilled (C:\hello-service.ts:5:58)
    at runNextTicks (internal/process/task_queues.js:58:5)
    at listOnTimeout (internal/timers.js:523:9)
    at processTimers (internal/timers.js:497:7)
(node:25960) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
. To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:25960) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
{"level":30,"time":1639745087914,"pid":25960,"hostname":"AZWP10801-12","reqId":"req-1","dd":{"trace_id":"2081604231398834164","span_id":"2081604231398834164","service":"@amerisave/example-service","version":"0.0.0"},"res":{"statu
sCode":200},"responseTime":1025.4359999895096,"msg":"request completed"}
(node:25960) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

一些依赖版本: 节点版本。 14.16.1 ts-node-dev 版本。 1.1.8 ts 节点版本。 9.1.1 打字稿版本。 4.5.2

为什么good 好,而bad 不好?

【问题讨论】:

    标签: node.js typescript asynchronous


    【解决方案1】:

    bad() 中的问题是因为 errorP 承诺在您到达 await errorP 之前被拒绝,因此当没有适当的拒绝处理程序时它会拒绝。 Nodejs 检测到一个 Promise 被拒绝并且你的进程返回到事件循环并且被拒绝的 Promise 上没有拒绝处理程序。这会收到“未处理的拒绝”警告。

    请注意,虽然await errorP 不直接应用拒绝处理程序,但它确实将errorP 绑定到父async 函数,该函数上确实有拒绝处理程序,因此await errorP 间接将拒绝处理分配给@ 987654327@。而errorP 本身只会拒绝并且不会导致父async 函数发生任何事情。它只是一个变量,包含一个现在被拒绝的承诺,上面没有拒绝处理程序。

    要利用 async 被拒绝承诺的自动错误传播,您必须 await 那个承诺。

    Nodejs 不知道您将在将来添加 await 以及将在未来某个时间执行的代码,因此它会报告未处理的拒绝。

    代码会受到这些类型的错误的影响,您将承诺放入变量中,并且您没有任何类型的承诺的拒绝处理程序,然后您在任何其他承诺上去await 在您提出任何类型的拒绝之前上一个承诺的处理程序。承诺只是坐在那里,没有错误处理。如果由于事情的时间安排,它碰巧在那个状态下被拒绝,你会得到警告。通常的解决方案是:

    1. 立即对 Promise 进行错误处理,这样它就永远不会自行停止。
    2. 在您准备好使用它之前不要创建承诺,但是您将使用它并进行适当的错误处理(使用 .then().catch()Promise.all().catch()await 或其他) .
    3. 当 Promise 位于没有任何拒绝处理的变量中时,不要 await 其他 Promise。

    我发现,如果我可以避免将一个没有任何处理的承诺放入变量中,而只是将承诺直接创建到将被监控完成和错误的环境中,那么你甚至没有普遍考虑这个问题。


    仅供参考,如果您在 nodejs 中运行它,您可以在此处以更简单的方式添加拒绝处理程序之前说明拒绝承诺的相同一般概念:

    function bad(t) {
        return new Promise((resolve, reject) => {
            setTimeout(reject, t);
        });
    }
    
    const b = bad(500);
    
    // this timer will fire after bad() rejects
    setTimeout(() => {
        b.catch(err => {
            console.log("caught b rejection");
        })
    }, 600);
    

    您将收到“未捕获的拒绝”错误,因为当 Promise 拒绝时,它还没有 .catch() 处理程序。您的代码也有同样的问题(尽管更模糊了一点),因为拒绝处理程序来自 awaitasync 函数,而 async 函数的调用者正在使用 try/catch

    【讨论】:

    • 您能否就提供的示例提供解决方案?在功能中存在多个等待的类似问题,其中稍后的等待依赖于以前的等待。代码将错误传播到上层函数,但开玩笑测试报告有未打开的句柄,并且还会得到 UnhandledPromiseRejectionWarning。
    • @malibeg - 一般的解决方案是永远不要立即创建一个没有 .catch() 处理程序的承诺。我必须查看您的代码才能知道如何为您提供帮助,因为这种东西非常特定于您的精确代码。您为什么不在自己的问题中发布您的代码和情况。如果您想让我看一下,请在您的问题上给我留言,并在其中添加@jfriend00,以引起我的注意。
    • 代码类似于给出的示例,只是有更多的嵌套函数,应该在顶级函数中处理异常。
    • @malibeg - 我不知道你对我有什么期望。在您自己的问题中向我展示您的代码,我可以建议如何修复您的代码。否则,永远不要将一个简单的承诺放入一个还没有 .catch() 处理程序的变量中。这是我能提供的唯一通用建议。如何修复您的特定代码取决于您的特定代码。当/如果您在新问题中发布代码时,我很乐意帮助您解决问题。
    【解决方案2】:

    这是一个假设(可以通过实验证明)。

    goodbad 之间的行为差​​异可以用awaits 的顺序来解释。

    bad 中,你在doSomething 上等待之后,在throwError 上等待,而在good 中,你在Promise.all 上等待,直到两者都被填满或至少都不会返回一个被拒绝(这里就是这种情况)。

    所以在bad 中,抛出发生在await外部,而你的catch 没有被触发,它被节点内部捕获。

    如果您将bad 更改为首先将await 设置为throwError,那么您的捕获将被触发:

    async bad(): Promise<string> {
        let errorP = this.throwError();
        let responseP = this.doSomething();
        let error = await errorP;
        let response = (await responseP) + '123';
        return response;
      }
    

    【讨论】:

    • 那么这是否意味着如果我有多个 Promise,我必须先等待它们,然后才能开始处理其中任何一个的输出?
    • 您建议的代码修复只是将responseP 承诺暴露给同样的错误。如果它在errorP 解决或拒绝之前拒绝,您只会在responseP 上收到未处理的拒绝。因此,虽然这使得这个硬编码序列的错误消失了,但这并不是解决问题的一般方法。有关一般解决方案,请参阅我的答案。
    • 是的,你是对的,它仅适用于这种情况,responseP 从不拒绝。主要的一点是,如果它抛出(或者正如你所说的“之前” - 这可能是一个更好的术语)await 之外,它不会被抓住。
    猜你喜欢
    • 2017-08-09
    • 1970-01-01
    • 1970-01-01
    • 2019-06-29
    • 1970-01-01
    • 2023-01-27
    • 1970-01-01
    • 1970-01-01
    • 2013-09-28
    相关资源
    最近更新 更多