【问题标题】:Awaiting two promises in try/catch block results in "Unhandled promise rejection" [duplicate]在 try/catch 块中等待两个承诺会导致“未处理的承诺拒绝”[重复]
【发布时间】:2020-08-20 06:32:41
【问题描述】:

我想等待两个并行运行的 Promise。我不想连续等待每个承诺(这有效但速度较慢)。

出于这个原因,我认为我可以首先创建两个 Promise 来让它们滚动,比如两个网络请求,然后等待它们并能够在 catch 块中捕获错误。这个假设似乎不正确,因为我在运行此示例代码时收到警告。

  • 这是为什么呢?
  • 我如何最好地使用优雅的代码并行运行两个或多个网络请求?
  • 为什么 Typescript 没有警告我 catch-block 不会 抓住拒绝?
async function testMultipleAwait() {
  try {
    const aPromise = new Promise((resolve) => {
      setTimeout(() => resolve('a'), 200);
    });

    const bPromise = new Promise((_, reject) => {
      setTimeout(() => reject('b'), 100);
    });

    const a = await aPromise;
    const b = await bPromise;
  } catch (e) {
    console.log('Caught error', e);
  }
}

testMultipleAwait();

不会导致“捕获错误”输出,而是得到

tsc test-try-catch-await.ts && node test-try-catch-await.js

(node:31755) UnhandledPromiseRejectionWarning: b
(node:31755) 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(). (rejection id: 1)
(node:31755) [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.
Caught error b
(node:31755) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

【问题讨论】:

    标签: node.js typescript async-await


    【解决方案1】:

    我想我知道问题出在哪里。即使 Promise 以并发方式启动,您仍在依次等待 aPromisebPromise

    const a = await aPromise; // First await this...
    const b = await bPromise; // and then start to await this
    

    当两个承诺都实现时,这不是什么大问题。它会让你等待Promise.all 的时间,然后愉快地继续。这就是为什么这个问题根本不那么明显的原因......

    重要的是要知道,由于 async/await,这个 try-catch 会在底层转换为 Promise。这意味着首先等待的语句之后的任何内容都将在 promise.then 回调函数中结束。

    因此,const b = await bPromiseconst a 到达之前(200 毫秒后)不会运行。 bPromise 提前 100 毫秒失败。

    这并不是说 async/await 不会发现错误或完全附加您的 catch 块(作为 promise.catch(...))。毕竟,节点警告和 catch 处理程序都有 终端输出:

    tsc test-try-catch-await.ts && node test-try-catch-await.js
    
    1 first node sees the error     > (node:31755) UnhandledPromiseRejectionWarning: b
    2                                 (node:31755) 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(). (rejection id: 1)
    3                                 (node:31755) [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.
    4 and then your catch handler   >      Caught error b
    5                                 (node:31755) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
    

    所以 de catch 子句确实有效,但异步函数至少在 200 毫秒后才将其附加到 bPromise。第 5 行似乎证实了这一点:

    PromiseRejectionHandledWarning: Promise rejection was handled asynchronously.
    

    一旦the microtask queue 为空,就会抛出拒绝错误。

    承诺拒绝已处理,但节点认为您为时已晚。您可以使用Promise.all 解决此问题。这样你await 一次,你的异步函数就会首先捕获每个潜在的错误。

    // Everything just as it is.. and then:
    
    const [a, b] = await Promise.all([
        aPromise, 
        bPromise,
    ]);
    

    因为好奇,我在 chrome 控制台中输入了您的代码,看看会发生什么。错误日志会在很短的时间内弹出(我猜是 100 毫秒)。看看这个输出,你可以听到 chrome 说:

    “啊等等!它终于被抓到了。这是消息!”

    Click for gif animation.

    【讨论】:

      【解决方案2】:

      等待几个Promises 解决以使用Promise.all 函数的好方法。它期望Promises 中的Array,并生成解析为ArrayArray,其中包含单个Promises 解析到的值。此外,它仅在最后一个 Promise 解析后才解析。如果它的任何输入Promises 拒绝,那么整个Promise.all 表达式也拒绝。它有效地“同时”“运行”所有输入进程,模仿经典的“fork-join”模式。

      您收到该错误的原因是因为您的超时过程是在您定义这些 Promises 时开始的,而不是在您 await 他们时,几行之后。如果您在 Promises 之一的定义中记录一些文本,然后在 await 表达式之前记录其他内容,您可以看到这一点:

      async function testMultipleAwait() {
        try {
          const aPromise = new Promise((resolve) => {
            console.log('starting timeout');
            setTimeout(() => resolve('a'), 200);
          });
      
          const bPromise = new Promise((_, reject) => {
            setTimeout(() => reject('b'), 100);
          });
      
          console.log('awaiting');
          const a = await aPromise;
          const b = await bPromise;
        } catch (e) {
          console.log('Caught error', e);
        }
      }
      
      testMultipleAwait();
      
      // logs:
      // "starting timeout"
      // "awaiting"
      

      要解决这个直接问题,您可以将这些 Promises 转换为函数,然后调用它们,或者 await 它们“立即”:

      // Option 1
      async function testMultipleAwait() {
        try {
          const aPromise = () => new Promise(resolve => {
            setTimeout(() => resolve("a"), 200);
          });
      
          const bPromise = () => new Promise((_, reject) => {
            setTimeout(() => reject("b"), 100);
          });
      
          const a = await aPromise();
          const b = await bPromise();
        } catch (e) {
          console.log("Caught error", e);
        }
      }
      
      testMultipleAwait();
      
      // Option 2
      async function testMultipleAwait() {
        try {
          await new Promise(resolve => {
            setTimeout(() => resolve("a"), 200);
          });
      
          await new Promise((_, reject) => {
            setTimeout(() => reject("b"), 100);
          });
        } catch (e) {
          console.log("Caught error", e);
        }
      }
      
      testMultipleAwait();
      

      将所有这些放在一起,让它们并行运行,您可以尝试这样的事情:

      async function testMultipleAwait() {
        try {
          await Promise.all([
            new Promise(resolve => {
              setTimeout(() => resolve("a"), 200);
            }),
            new Promise((_, reject) => {
              setTimeout(() => reject("b"), 100);
            })
          ]);
        } catch (e) {
          console.log("Caught error", e);
        }
      }
      
      testMultipleAwait();
      

      【讨论】:

        【解决方案3】:

        让我们看看我能不能用通俗易懂的方式解释一下,我们开始吧……

        在创建bPromise 后,您的setTimeout-call 回调(即promise 的拒绝)被“推送”到事件循环的timersqueue 中。现在,当事件循环进入下一次迭代时,promise 队列还没有处理await bPromise,但由于大约 100 毫秒已经过去,回调被执行并且你得到Unhandled promise rejection 错误。

        您需要立即await 新创建的promise,因为这次事件循环的promise microtask-queue 不会为空,现在将在处理timers-queue 中的回调之前执行,因此是能够真正捕捉到错误:

        const bPromise = await new Promise((_, reject) => {
              setTimeout(() => reject('b'), 100);
        });
        

        我建议阅读 thisthis 以更好地了解事件循环的工作原理。

        【讨论】:

        • 为什么会过了 100 毫秒?如果我将时间设置为 8000 和 7000,也会出现同样的错误。请注意,切换时间顺序会使代码正常工作。
        • 很奇怪,也许我遗漏了一些东西,我会调查一下——与此同时,也许其中一位专业人士可以帮助你。对不起:)
        猜你喜欢
        • 1970-01-01
        • 2018-07-09
        • 2020-01-01
        • 2021-08-02
        • 2019-02-21
        • 2018-12-13
        • 2017-06-04
        • 1970-01-01
        相关资源
        最近更新 更多