【问题标题】:Apparent superfluous "unhandledRejection" event fired触发了明显多余的“unhandledRejection”事件
【发布时间】:2019-05-23 02:44:45
【问题描述】:

我们在我们的节点应用程序中为"unhandledRejection" 添加了一个事件处理程序,这个处理程序终止了进程(正如节点所说的将是默认的很快)。

但是,我们看到的事件似乎是多余的(或至少为时过早的)。

我们正在使用Q.allSettled(它处理被拒绝的承诺),我在调试器中看到承诺数组有两个元素,第一个 rejected 和第二个 pending(它将被解决)。在unhandledRejection 处理程序和allSettled 之后的then 中放置断点后,我首先得到unhandledRejection,然后立即得到then(因此拒绝 处理)。

unhandledRejection 的文档说(强调我的):

'unhandledRejection' 事件在 Promise 被拒绝并且没有错误处理程序附加到 Promise 时发出在事件循环的一个轮次中。

我的假设是创建 promise 数组的机制在第一个 promise 被拒绝和数组创建之间阻塞,但我无法在玩具示例中重现这一点。

我能想出的最好的最小示例有点做作,但我认为它与我们的代码具有相同的根本原因。我在这里错过了什么吗?如果可以的话,node如何让unhandledRejection默认终止进程?

process.on("unhandledRejection", (reason, p) => {
    console.error(`### Got unhandled rejection: '${reason}'`);
});


function doesNotFireUnhandledRejection() {
    let rejected = Promise.reject('an error');
    return new Promise((resolve, reject) => {
        resolve(rejected);
    })
    .catch(err => console.log("Caught:", err));
}


function firesUnhandledRejection() {
    let rejected = Promise.reject('an error');
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(rejected), 0);
    })
    .catch(err => console.log("Caught:", err));
}

顺便说一句,setImmediate 会发生相同的行为,但 process.nextTick 不会发生相同的行为(尽管我可以发誓上周 process.nextTick 确实导致事件触发...)。

【问题讨论】:

  • 我知道您正在尝试创建一个最小示例来重现它,但是静态拒绝承诺的原因是什么,取回 promise 对象,然后将其包装在 resolve 中另一个承诺?
  • @quirimmo,没有。我试图创建一个更有意义的示例,但还没有成功(还没有?)
  • 啊,好吧,我想我现在知道你的问题了。你的意思是,如果你引入超时,为什么第一次拒绝没有被捕获?
  • @blex 在现实生活中,创建原始拒绝的函数在下游,对于大多数返回常规拒绝的调用者来说很好。没有合理的理由将其(及其所有客户)更改为使用包装承诺。

标签: javascript node.js promise


【解决方案1】:

这是如何在未处理的拒绝时终止进程(预计在未来版本中将成为默认节点行为):

process.on('unhandledRejection', err => {
  console.error(err);
  process.exit(1);
});

被拒绝的承诺应该与catch(...)then(..., ...) 链接在同一个tick 上,否则它被认为是未处理的拒绝。

在保证稍后处理拒绝的极少数情况下,可以将 promise 与 dummy catch 链接,以抑制对未处理拒绝的检测:

function firesUnhandledRejection() {
    const rejectedPromise = Promise.reject('an error');
    rejectedPromise.catch(() => {});

    return new Promise((resolve) => {
        setTimeout(() => resolve(rejectedPromise), 0);
    })
    .catch(err => console.log("Caught:", err));
}

【讨论】:

  • 哇,我偶然看到了这个答案,就在 my very similar question 上的劣质答案授予 +500 赏金之后。介意在那里发布您的答案,以便我可以在那里接受吗?我保证也会有赏金。另外,我假设第二行应该是() => {},或者可能是e => {},不是吗?
  • 感谢您的注意,这是一个错字。实际上,我不确定这是否是您案例的最佳解决方案。通常我会选择 Promise.all,因为它更接近实际情况,因为有两个并发例程。稍后我会回复。
  • 问题是返回被拒绝的Promise 的函数不知道何时以及如何使用返回的Promise。因此,我不知道您如何决定何时使用 dummy catch 方法,此外,通过使用 dummy catch 方法,您将失去诊断真正未处理的拒绝的能力.
  • @Motti .catch(() => {}) 应该在firesUnhandledRejection 内部使用,您已经知道将处理拒绝,因为您使用另一个catch 处理它。请注意,您问题中的函数不会返回被拒绝的承诺,因此它们不会显示您所描述的情况。请提供一个更紧密地复制您的案例的示例。
【解决方案2】:

回答你的问题:

  • 具有微任务语义的事情(process.nextTick、then 处理程序、异步函数的延续和 enqueueMicrotask)发生在未处理的拒绝检查之前。
  • 具有宏任务语义(定时器、i/o)的事情发生在未经处理的拒绝检查之后。

这就是我们所说的:“在事件循环的一个回合内”。

在这种情况下,Node.js 的行为符合预期。请注意,在您的情况下:

function firesUnhandledRejection() {
    let rejected = Promise.reject('an error'); // < -- this is the unhandled rejection
    return new Promise((resolve, reject) => { // <- this promise is totally fine
        setTimeout(() => resolve(rejected), 0); // <- you associate them too late
    })
    .catch(err => console.log("Caught:", err));
}

所以问题是太长时间(定时器触发)有一个没有错误处理程序的被拒绝的承诺。

【讨论】:

  • 这也是我理解事物的方式,但它没有回答问题。如果某些合法流导致在一个滴答过后处理拒绝,我们如何检测 true 未处理的拒绝以及在这种情况发生时我们如何防止节点退出?
  • @Motti 我会直言不讳 - unhandledRejection 事件及其时间是自以为是的(我是这样做的)。由于检测到所有真正的未处理拒绝实际上是停止问题,Node 会做出明智的决定来阻止异步处理拒绝。顺便说一下,我个人完全避免使用 Promise 构造函数。我们有基于 GC 的未处理拒绝检测,但这不会很快发生。
  • 如果您想避免误报,我们还会公开一个“rejectionHandled”事件来检测“误报”。
  • @Motti 最困难的部分是 - 你怎么知道拒绝是否没有得到处理?顺便说一句 - 我们非常愿意接受关于如何在 Node.js 中推进这一点的想法和反馈。顺便说一句,弃用警告并不完全正确。
  • 除了等待我不知道的任意时间(显然不是一个好主意)。如果我想到了什么,我一定会告诉你的。
猜你喜欢
  • 1970-01-01
  • 2023-01-14
  • 2020-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-23
  • 2014-08-02
相关资源
最近更新 更多