146

为什么我不能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会被黑洞吞噬?

4

7 回答 7

172

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

再添加一个 catch 以查看发生了什么:

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 控制台中的链接将我直接带到发生(原始)错误的文件和行。

为什么有效

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

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

于 2015-06-09T20:01:15.477 回答
50

重要的事情要在这里了解

  1. then和函数都catch返回新的 Promise 对象。

  2. 无论是抛出还是明确拒绝,都会将当前的承诺移动到拒绝状态。

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

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

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


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

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

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

由于您没有将任何拒绝处理程序附加到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 的值视为已拒绝。

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

于 2015-06-08T17:37:30.813 回答
8

我试过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;
});
于 2016-04-12T21:09:39.470 回答
4

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

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

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

我猜你想在你的代码中记录未处理的拒绝。大多数承诺图书馆unhandledRejection都会为此而努力。这是有关它的讨论的相关要点。

于 2015-06-08T17:53:52.250 回答
1

是的,承诺会吞下错误,您只能使用 捕获它们.catch,如其他答案中更详细说明的那样。如果您在 Node.js 中并且想要重现正常throw行为,将堆栈跟踪打印到控制台并退出进程,您可以这样做

...
  throw new Error('My error message');
})
.catch(function (err) {
  console.error(err.stack);
  process.exit(0);
});
于 2015-11-07T00:00:29.933 回答
1

我知道这有点晚了,但是我遇到了这个线程,并且没有一个解决方案对我来说很容易实现,所以我想出了自己的:

我添加了一个返回承诺的小辅助函数,如下所示:

function throw_promise_error (error) {
 return new Promise(function (resolve, reject){
  reject(error)
 })
}

然后,如果我在我的任何承诺链中有一个特定位置要抛出错误(并拒绝承诺),我只需从上面的函数返回我构造的错误,如下所示:

}).then(function (input) {
 if (input === null) {
  let err = {code: 400, reason: 'input provided is null'}
  return throw_promise_error(err)
 } else {
  return noterrorpromise...
 }
}).then(...).catch(function (error) {
 res.status(error.code).send(error.reason);
})

这样我就可以控制从承诺链内部抛出额外的错误。如果您还想处理“正常”承诺错误,您可以扩展您的捕获以分别处理“自我抛出”错误。

希望这会有所帮助,这是我的第一个 stackoverflow 答案!

于 2018-07-08T09:09:05.960 回答
0
  1. 监听未处理的错误:
window.addEventListener('unhandledrejection', e => {
  // ...
});
window.addEventListener('error', e => {
  // ...
});
  1. 如果错误被吞下,请使用self.report(error)
.catch(error => {
  self.reportError(error);
});

https://developer.mozilla.org/en-US/docs/Web/API/reportError

于 2021-12-30T20:49:20.317 回答