【问题标题】:await in finally block of async function causes PromiseRejectionHandledWarning在异步函数的 finally 块中等待导致 PromiseRejectionHandledWarning
【发布时间】:2020-08-03 21:05:59
【问题描述】:

我在我的 NodeJs 代码中使用了asyncawait,代码结构如下。

async function main(){
    try {
        await someFunctionThatReturnsRejectedPromise()
    } catch(e) {
        console.log(e)
    }
}

async function someFunctionThatReturnsRejectedPromise() {
    try {
        await new Promise((resolve,reject) => {
            setTimeout(() => {
                reject('something went wrong')
            }, 1000);
        })
    } catch(e) {
        return Promise.reject(e)
    } finally {
        await cleanup() // remove await here and everything is fine
    }
}


function cleanup() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('cleaup successful')
        }, 1000);
    })
}

main();

在 finally 块中,我正在做一些async 清理工作,肯定会解决。 但是这段代码正在抛出PromiseRejectionHandledWarning

(node:5710) UnhandledPromiseRejectionWarning: something went wrong
(node:5710) 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:5710) [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.
something went wrong
(node:5710) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

据我所知,我不会在这里留下任何未处理的承诺。 我究竟做错了什么? finally 是否应该按设计同步阻止?如果是,为什么会这样?

更新 1:

如果我将someFunctionThatReturnsRejectedPromise 转换为良好的thencatch,它可以正常工作:

function someFunctionThatReturnsRejectedPromise() {
    return (new Promise((resolve,reject) => {
        setTimeout(() => {
            reject('something went wrong')
        }, 1000);
    })).catch(e => {
        return Promise.reject(e)
    }).finally(() => {
        return cleanup()
    })
}

更新 2:(了解问题)

如果我await返回Promise,问题就解决了。

 return await Promise.reject(e)

这让我明白我做错了什么。 我打破了await 链(部分同义于在then/catch 语法中不返回Promise)。 谢谢大家:)

【问题讨论】:

  • 抛出“DeprecationWarning”是因为您没有在cleanup 中为您的Promise 提供拒绝策略。你永远不应该使用 Promise 构造函数(除非在一些非常特殊的非常罕见的情况下)。您还应该在await cleanup() 上添加try-catch 以处理可能的未处理拒绝。
  • @briosheje 正如我所提到的,“肯定会解决的清理”把它想象成一个永远不会抛出/拒绝的函数
  • 再一次:只要涉及await,就会创建Promise。如果你不处理该承诺的拒绝,节点会给你这样的警告。这是有意的,因为承诺可能会因任何类型的错误而被拒绝(无论是什么通用的 JAVASCRIPT 错误,不一定与承诺相关)。您必须删除 await,或者用 try-catch 包围块。
  • @briosheje 即使我处理cleanup,问题仍然存在

标签: javascript node.js promise async-await es6-promise


【解决方案1】:

当 Promise 拒绝时,必须在当前调用堆栈清除之前处理,否则将出现未处理的拒绝。你有:

} catch (e) {
  return Promise.reject(e)
} finally {
  await cleanup() // remove await here and everything is fine
}

如果您删除 awaitsomeFunctionThatReturnsRejectedPromise 将返回 立即在被拒绝的 Promise 被构造之后,所以被拒绝的 Promise Promise.reject(e)main 中的catch 捕获。但是如果有任何延迟,被拒绝的 Promise 将不会立即处理;你的await cleanup()将意味着被拒绝的Promise在someFunctionThatReturnsRejectedPromise返回之前有一段时间没有被处理,这意味着maincatch无法及时处理被拒绝的Promise。

您可以使用的另一种方法是将错误包装在Error 而不是Promise.reject 中,然后检查结果是否是main 中的instanceof Error

window.addEventListener('unhandledrejection', () => console.log('unhandled rejection!'));

async function main() {
  const result = await someFunctionThatReturnsRejectedPromise();
  if (result instanceof Error) {
    console.log('Error "caught" in main:', result.message);
  }
}

async function someFunctionThatReturnsRejectedPromise() {
  try {
    await new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('something went wrong')
      }, 1000);
    })
  } catch (e) {
    return new Error(e);
  } finally {
    await cleanup()
  }
}


function cleanup() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('cleaup successful')
    });
  })
}

main();

【讨论】:

  • 不确定我是否理解正确。 Javascript 在单个线程上工作。当您说“Promise 在一段时间内未处理”时,这是什么意思?解释器仍在函数中。功能仍在执行中。 Node 如何控制尚未返回的 Promise?此外,如果您看到,如果我将someFunctionThatReturnsRejectedPromise 转换为thencatch,它可以正常工作。 try, catch and finally 只是语法,它们的意思应该是一样的,不是吗?
  • 解释器仍在函数中 不完全正确 - 在创建被拒绝的 Promise 和 cleanup 的 @987654343 之间的时间里,控制流在 someFunctionThatReturnsRejectedPromise 内@ 被排队,但随后控制权被退回,其他任务可能同时运行。当遇到await 时,Javascript 执行不会完全停止 - 它只会暂停await 所在的当前函数
  • 查看 this fiddle 以获取在 Promise 被 awaited 时运行的其他 JS 代码的示例
  • 感谢您抽出宝贵时间 :) 我部分了解,但仍不确定为什么会发生这种情况,将对此进行更多研究
【解决方案2】:

更新答案替换

Promise.reject(e)throw e;

所以函数变成了

async function someFunctionThatReturnsRejectedPromise() {
   try {
       await new Promise((resolve,reject) => {
           setTimeout(() => {
               reject('something went wrong')
           }, 1000);
       })
   } catch(e) {
       throw e;
   } finally {
       await cleanup() // remove await here and everything is fine
   }
}

原因

someFunctionThatReturnsRejectedPromise 方法首先拒绝Promise。所以控制流转到方法main catch 块。 后来cleanup 方法尝试做同样的事情。也就是拒绝已经拒绝的承诺。因此你得到错误

Promise.rejectthrow 子句有点不同。请参考throw vs Promise.reject

这就是为什么从 cleanup() 中删除 await 或从 cleanup 中删除 return 方法有效的原因。因为这会将Promise 从当前控制流中分离出来。

【讨论】:

  • 还是同样的问题
  • 更新的原因。请检查
猜你喜欢
  • 2018-05-27
  • 2020-01-09
  • 2014-09-12
  • 2023-03-13
  • 1970-01-01
  • 2017-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多