【问题标题】:How to handle an unhandled promise rejection asynchronously?如何异步处理未处理的承诺拒绝?
【发布时间】:2020-03-22 10:29:59
【问题描述】:

我正在努力解决我所面临的关于 async/await 和 Promises 的问题。我设法将我的问题归结为以下代码:

async function sleep(ms: number) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

async function fetchMock(): Promise<any> {
  return new Promise(() => {
    throw 'error fetching result';
  });
}

async function main(): Promise<any> {
  const kickedOffRequest = fetchMock();
  await sleep(10);
  return kickedOffRequest;
}

main()
  .then(() => console.log('resolved promise!'))
  .catch(error => console.error('caught error!', error));

我收到以下警告:

(node:82245) UnhandledPromiseRejectionWarning: error fetching result
(node:82245) 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:82245) [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! error fetching result
(node:82245) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

您可以观察到相同的问题in this sandbox。我注意到注释掉await sleep(10) 解决了这个问题,但显然我对承诺的了解比我想象的要少。为什么评论该行会使我的程序正常工作?我很想问如何修复Promise rejection was handled asynchronously 错误,但我希望一旦我了解await sleep(10) 如何导致我得到的错误,我将能够自己修复这个错误。

提前感谢您花时间阅读/回答这个问题!

【问题讨论】:

  • @Bergi - 我看不到您标记的副本实际上如何为这个特定问题提供解决方案。 OP 在main() 中似乎想要的是从fetchMock() 返回承诺,无论是被拒绝还是已解决,但直到至少 10 毫秒过去后才解决或拒绝。我不明白如何在你的重复中做到这一点。因此,如果fetchMock() 立即解析或拒绝,则从main() 返回的承诺在 10 毫秒内不会解析或拒绝。但是,如果来自fetchMock() 的承诺花费了超过 10 毫秒来解决或拒绝,则不会在该解决或拒绝中增加进一步的延迟。
  • @jfriend00 对我来说,不清楚 OP 真正想要什么行为,但欺骗是我们的规范解释为什么当前的尝试不起作用以及为什么应该使用 Promise.all
  • 好吧,Promise.all() 没有明显的用途,它创建了main() 显示的确切时间。 Canonical answers 适用于规范问题,而不是引发不同变体且规范答案中未显示解决方案的问题。
  • @jfriend00 这正是我想要的,谢谢。在我的“真实”程序中,我启动了一个网络请求,然后休眠了一段时间。我现在不想等待我的网络请求,所以Promise.all 不是我想要的东西。
  • 您是否想要一个与您描述的一样的答案 - 包括拒绝返回的承诺而不是您已经接受的答案中的解决方法?我想出了一个办法,但我需要一些时间来确保它正常工作。

标签: javascript error-handling promise async-await


【解决方案1】:

Promise 的最初概念是,在将 catch 处理程序附加到它之前,您可以让一个被拒绝的 Promise 存在一段时间。例如,Firefox 过去仅在没有拒绝处理程序的被拒绝承诺从内存中收集垃圾时才会警告未捕获的拒绝错误。

有人认为程序员不能被信任正确地管理 Promise 拒绝,并更改了 HTML 规范,要求浏览器在代码返回事件循环之前没有添加拒绝处理程序时抛出“未处理的 Promise 拒绝”错误。

(我认为在控制权返回到正确的事件循环之前,未处理的拒绝可以在微任务队列中存活一两次而不会出现错误,但最近没有对其进行测试。)

ECMAScript 规范添加了notifying the host environment of an unhandled promise rejection 的抽象表示,但没有指定应该采取什么行动(如果有的话)。

根据具体情况,您可以通过添加一个从未使用过的拒绝处理程序来防止通知主机。原因是,向 Promise 添加一个虚拟拒绝处理程序意味着如果它被拒绝,它已经有一个拒绝处理程序 - 或者如果它被拒绝,主机会收到通知,该承诺现在有一个拒绝处理程序 - 你可以调用 thencatch 多次在同一个承诺上。

变化

async function fetchMock(){
  return new Promise(() => {
    throw 'error fetching result';
  });
}

async function fetchMock(){
  let promise = new Promise(() => {
    throw 'error fetching result';
  });
  promise.catch(()=>null); // unused rejection handler
  return promise;
}

应该解决在 V8(node 中使用的 JavaScript 引擎)中实现的不需要的 HTML5 主机行为。

【讨论】:

    【解决方案2】:

    node.js 中未处理拒绝的检测是不完善的。在被拒绝的 Promise 的生命周期中有特定的位置,引擎会检查是否有处理程序,并且它并不总是等到最后可能的时刻,因此它可能会错过您添加处理程序的地方。在您的特定情况下,您可能需要在本地附加一个 .catch() 处理程序,然后完成您想要做的工作,然后重新抛出错误。这种变通方法对您有用,同时仍保持来自main() 的所需解析/拒绝(例如,无需将界面更改为主界面)。

    所以,这不是特别漂亮,但它符合我们在 cmets 中讨论的规范。

    1. main() 致电fetchMock()
    2. 如果它快速解决或拒绝(在某个自定义延迟时间之前),则它会推迟解决或拒绝,直到至少从最初调用 fetchMock() 开始经过该延迟时间。
    3. 如果fetchMock() 解决或拒绝的时间长于自定义延迟时间,则不会添加更多延迟。
    4. main() 返回的 Promise 遵循 fetchMock() 返回的 Promise,被拒绝或以相同的原因或值解决。

    关键因素是它在调用fetchMock() 之前捕获时间,然后当fetchMock() 解决或拒绝时,它决定是否在传递解决/拒绝值/原因之前再延迟时间。

    function sleep(ms) {
        return new Promise(resolve => {
            setTimeout(resolve, ms);
        });
    }
    
    function fetchMock() {
        return new Promise((resolve) => {
            throw 'error fetching result';
            //resolve('this is our result');
        });
    }
    
    
    function handler(start, minWaitTime, isErr = false) {
        return async function(val) {
            let diff = minWaitTime - (Date.now() - start);
            if (diff > 0) {
                await sleep(diff);
            }
            if (isErr) {
                throw val;
            } else {
                return val;
            }
        }
    }
    
    function main() {
        let start = Date.now();
        const minWaitTime = 1000;
        return fetchMock().then(handler(start, minWaitTime), handler(start, minWaitTime, true));
    }
    
    main()
        .then(() => console.log('resolved promise!'))
        .catch(error => console.error('caught error!', error));

    请注意,sleep()fetchMock() 已经直接返回 Promise 并且不使用 await,因此不需要它们是 async

    【讨论】:

    • @marcelHaupenthal - 好的,这是一种方法。
    • 你太棒了!谢谢你花时间陪我。我认为这是我一直在寻找的解决方案。我将不得不阅读更多关于 Promise 的文档,但这对我有很大帮助!祝你有美好的一天。
    【解决方案3】:

    问题是fetchMock 立即拒绝,当 Promise 拒绝时,在它拒绝时,它必须与 .catch 链接为了防止未处理的承诺拒绝。

    使用您的await sleep(10)kickedOffRequest 承诺拒绝,而main 函数仍在等待sleep 解决。当出现拒绝时,解释器不会向前看来查看未来是否会捕获 Promise(例如,查看 Promise 是否被返回或捕获) - 必须捕获 Promise 现在

    当你删除 await 时,kickedOffRequest 仍然成为一个被拒绝的 Promise,但它是从 main 返回的立即,所以在 Promise 被拒绝的那一刻,它可以被看到并且被外部.catch 捕获,因此没有未处理的拒绝警告。


    如果 Promise 拒绝可以在正确被拒绝时处理,那很好,但如果这不是你的选择,你可以在内部 Promise 的末尾添加另一个 .catch。这样,警告就不会出现(因为 .catch 的存在,即使 .catch 没有做任何有意义的事情),您可以稍后检查是否真的有错误发生与否:

    async function sleep(ms) {
      return new Promise(resolve => {
        setTimeout(resolve, ms);
      });
    }
    
    async function fetchMock(){
      return new Promise(() => {
        throw 'error fetching result';
      });
    }
    
    async function main() {
      const kickedOffRequest = fetchMock()
        .then(resolved => ({ resolved }))
        .catch(rejected => ({ rejected }));
      await sleep(10);
      return kickedOffRequest;
    }
    
    main()
      .then(({ resolved, rejected }) => {
        if (resolved) {
          console.log('resolved promise!');
        } else {
          console.error('caught error!', rejected);
        }
      });

    这与 Promise.allSettled 的工作方式非常相似(尽管它更适用于需要解析的 Promise array):

    async function sleep(ms) {
      return new Promise(resolve => {
        setTimeout(resolve, ms);
      });
    }
    
    async function fetchMock(){
      return new Promise(() => {
        throw 'error fetching result';
      });
    }
    
    async function main() {
      const kickedOffRequestArr = Promise.allSettled([fetchMock()]);
      await sleep(10);
      return kickedOffRequestArr;
    }
    
    main()
      .then(([{ result, reason, value }]) => {
        if (result === 'fulfilled') {
          console.log('resolved promise!', value);
        } else {
          console.error('caught error!', reason);
        }
      });

    【讨论】:

    • 谢谢!这样就搞清楚了。我不确定我对返回一个解决为围绕错误或实际响应的“包装器”的 Promise 的感受。我喜欢在嵌套函数中抛出错误并让main 的用户对错误做出反应,但如果我理解正确,那真的不可能,是吗?
    • 对。这是可能的,但只是勉强 - 由于拒绝警告,这不是一个好主意,这最终会成为 Node 的错误(此时实际上是不可能的)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-21
    • 2021-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-31
    相关资源
    最近更新 更多