为什么我不能在 Promise.catch 处理程序中抛出?

IT技术 javascript asynchronous promise throw es6-promise
2021-01-19 04:52:37

为什么我不能只Error在 catch 回调中抛出一个并让进程处理错误,就好像它在任何其他范围内一样?

如果我什么都不做,console.log(err)就会被打印出来,我对发生的事情一无所知。过程刚刚结束...

例子:

function do1() {
    return new Promise(function(resolve, reject) {
        throw new Error('do1');
        setTimeout(resolve, 1000)
    });
}

function do2() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            reject(new Error('do2'));
        }, 1000)
    });
}

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // This does nothing
});

如果回调在主线程中执行,为什么Error会被黑洞吞噬?

6个回答

正如其他人所解释的那样,“黑洞”是因为在 a 中抛出.catch一个被拒绝的Promise继续链,并且你没有更多的捕获,导致一个未终止的链,它吞下了错误(糟糕!)

添加更多捕获以查看发生了什么:

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // Where does this go?
}).catch(function(err) {
    console.log(err.stack); // It goes here!
});

当您希望链在执行失败的情况下继续执行时,链中间的捕获很有用,但重新抛出对于在执行诸如记录信息或清理步骤之类的操作后继续失败很有用,甚至可能更改哪个错误被抛出。

诡计

为了使错误在 Web 控制台中显示为错误,正如您最初打算的那样,我使用了以下技巧:

.catch(function(err) { setTimeout(function() { throw err; }); });

甚至行号仍然存在,因此 Web 控制台中的链接将我直接带到发生(原始)错误的文件和行。

为什么有效

被称为Promise履行或拒绝处理程序的函数中的任何异常都会自动转换为对您应该返回的Promise的拒绝。调用您的函数的Promise代码会处理这个问题。

另一方面,由 setTimeout 调用的函数始终从 JavaScript 稳定状态运行,即它在 JavaScript 事件循环中的新循环中运行。异常没有被任何东西捕获,并将其发送到 Web 控制台。由于err保存了有关错误的所有信息,包括原始堆栈、文件和行号,因此仍然可以正确报告。

@jib 我正在编写一个 AWS lambda,其中包含许多或多或少像这种情况下连接的Promise。为了在出现错误时利用 AWS 警报和通知,我需要让 lambda 崩溃引发错误(我猜)。技巧是获得这个的唯一方法吗?
2021-03-21 04:52:37
@StijndeWitt 就我而言,我试图在window.onerror事件处理程序中将错误详细信息发送到我的服务器只有做到setTimeout这一点,才能做到这一点。否则window.onerror永远不会听到有关 Promise 中发生的错误的消息。
2021-03-23 04:52:37
@hudidit 尽管如此,无论是console.log还是postErrorToServer,您都可以做需要做的事情。任何代码都没有理由window.onerror不能分解成一个单独的函数并从两个地方调用。它可能比这setTimeout条线还要短
2021-03-24 04:52:37
Jib,这是一个有趣的技巧,你能帮我理解为什么会这样吗?
2021-04-13 04:52:37
关于那个技巧:你投掷是因为你想记录,那为什么不直接记录呢?这个技巧会在一个“随机”的时间抛出一个无法捕捉的错误......但是异常的整个想法(以及Promise处理它们的方式)是让调用者有责任捕捉错误并处理它。这段代码有效地使调用者无法处理错误。为什么不做一个函数来为你处理呢?function logErrors(e){console.error(e)}然后像do1().then(do2).catch(logErrors). 顺便说一句,答案本身很棒,+1
2021-04-13 04:52:37

这里需要了解的重要事项

  1. 无论是thencatch函数返回新Promise的对象。

  2. 抛出或明确拒绝,会将当前Promise移动到拒绝状态。

  3. 由于thencatch返回新的Promise对象,它们可以被链接起来。

  4. 如果在Promise处理程序(thencatch)中抛出或拒绝,它将在链接路径下的下一个拒绝处理程序中处理。

  5. 正如 jfriend00 所提到的,thencatch处理程序不是同步执行的。当处理程序抛出时,它将立即结束。因此,堆栈将展开,异常将丢失。这就是为什么抛出异常会拒绝当前的Promise。


在你的情况下,你是do1通过扔一个Error物体来拒绝内部现在,当前的Promise将处于拒绝状态,控制权将转移到下一个处理程序,then在我们的例子中。

由于then处理程序没有拒绝处理程序,因此do2根本不会执行。您可以通过console.log在其中使用来确认这一点。由于当前的 Promise 没有拒绝处理程序,它也将被前一个Promise的拒绝值拒绝,并且控制权将转移到下一个处理程序,即catch

catch拒绝处理程序一样,当您console.log(err.stack);在其中执行操作时,您可以看到错误堆栈跟踪。现在,您正在Error从中抛出一个对象,因此返回的Promisecatch也将处于拒绝状态。

由于您没有将任何拒绝处理程序附加到catch,因此您无法观察到拒绝。


您可以拆分链条并更好地理解这一点,就像这样

var promise = do1().then(do2);

var promise1 = promise.catch(function (err) {
    console.log("Promise", promise);
    throw err;
});

promise1.catch(function (err) {
    console.log("Promise1", promise1);
});

您将获得的输出将类似于

Promise Promise { <rejected> [Error: do1] }
Promise1 Promise { <rejected> [Error: do1] }

catch处理程序 1 中,您将获得promiseobject的值作为拒绝。

同样,catch处理程序 1返回的Promise也被拒绝,并出现与promise被拒绝相同的错误,我们正在第二个catch处理程序中观察它

可能还值得补充的是,.then()处理程序是异步的(在执行之前堆栈已展开),因此必须将其中的异常转换为拒绝,否则将没有异常处理程序来捕获它们。
2021-03-30 04:52:37

我尝试了setTimeout()上面详述方法...

.catch(function(err) { setTimeout(function() { throw err; }); });

令人讨厌的是,我发现这是完全无法测试的。因为它抛出一个异步错误,所以你不能将它包装在一个try/catch语句中,因为在catch抛出错误时它将停止监听。

我恢复到只使用一个完美的监听器,因为它是 JavaScript 的使用方式,所以是高度可测试的。

return new Promise((resolve, reject) => {
    reject("err");
}).catch(err => {
    this.emit("uncaughtException", err);

    /* Throw so the promise is still rejected for testing */
    throw err;
});
Jest 有可以处理这种情况的计时器模拟
2021-04-03 04:52:37

根据规范(见 3.III.d)

d. 如果调用则抛出异常 e,
  a。如果调用了 resolvePromise 或 rejectPromise ,则忽略它。
  湾 否则,以 e 为理由拒绝 promise。

这意味着如果您在then函数中抛出异常,它将被捕获并且您的Promise将被拒绝。catch在这里没有意义,这只是捷径.then(null, function() {})

我猜您想在代码中记录未处理的拒绝。大多数Promise库会unhandledRejection为它触发一个这是有关它的讨论的相关要点

值得一提的是,unhandledRejectionhook 是针对服务端的 JavaScript 的,在客户端,不同的浏览器有不同的解决方案。我们还没有对其进行标准化,但它正在缓慢但肯定地到达那里。
2021-04-11 04:52:37

Yes Promise会吞下错误,你只能用 来捕捉它们.catch,如其他答案中更详细的解释。如果您在 Node.js 中并想要重现正常throw行为,将堆栈跟踪打印到控制台并退出进程,您可以这样做

...
  throw new Error('My error message');
})
.catch(function (err) {
  console.error(err.stack);
  process.exit(0);
});
Bluebird,Q,when,native promise,……它很可能会成为一个标准。
2021-03-24 04:52:37
是的,那是假设你链接了你的Promise,所以退出是最后一个函数,它不会被捕获。您提到的事件我认为只有在使用 Bluebird 时才会发生。
2021-04-03 04:52:37
不,这还不够,因为您需要将其放在您拥有每个Promise链的末尾而是挂在unhandledRejection事件上
2021-04-13 04:52:37