如何异步处理未处理的承诺拒绝?

mar*_*upe 7 javascript error-handling promise async-await

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

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));
Run Code Online (Sandbox Code Playgroud)

我收到以下警告:

(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)
Run Code Online (Sandbox Code Playgroud)

您可以在此沙箱中观察到相同的问题。我注意到注释掉可以await sleep(10)解决问题,但显然我对 promise 的了解比我想象的要少。为什么注释该行会使我的程序运行?我很想询问如何修复Promise rejection was handled asynchronously错误,但我希望一旦我了解await sleep(10)了错误的原因,我将能够自己修复这个错误。

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

tra*_*r53 8

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

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

(我认为未处理的拒绝可以在微任务队列中存活一两个滴答,然后控制返回到适当的事件循环,但最近没有对其进行测试。)

ECMAScript 规范添加了一种抽象方法,用于将未处理的承诺拒绝通知主机环境,而无需指定应采取的操作(如果有)。

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

改变

async function fetchMock(){
  return new Promise(() => {
    throw 'error fetching result';
  });
}
Run Code Online (Sandbox Code Playgroud)

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

应该可以解决 V8 中实现的不需要的 HTML5 主机行为,V8 是 node.js 中使用的 JavaScript 引擎。


jfr*_*d00 7

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

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

  1. main()来电fetchMock()
  2. 如果它快速解决或拒绝(在某个自定义延迟时间之前),那么它会推迟解决或拒绝,直到至少从fetchMock()最初调用时起的延迟时间过去。
  3. 如果fetchMock()解决或拒绝所需的时间长于自定义延迟时间,则不再添加延迟。
  4. 然后返回的 Promisemain()遵循返回的 Promise fetchMock(),以相同的原因或值被拒绝或解决。

关键因素是它捕获调用之前的时间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));
Run Code Online (Sandbox Code Playgroud)

另外请注意,sleep()andfetchMock()已经直接返回承诺并且不使用await,因此不需要它们是async.