为什么我不能放进一个“承诺,抓住处理器”里面?

为什么我不能在 catch 回调中抛出一个 Error,让进程像处理其他作用域一样处理这个错误呢?

如果我不做 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会被黑洞吞噬?

107098 次浏览

重要的是要明白

  1. thencatch函数都返回新的承诺对象。

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

  3. 由于 thencatch返回新的承诺对象,因此可以对它们进行链接。

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

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


在这种情况下,抛出一个 Error对象就是在 do1内部拒绝。现在,当前的承诺将处于拒绝状态,控制将被转移到下一个处理程序,在我们的例子中是 then

因为 then处理程序没有拒绝处理程序,所以根本不会执行 do2。您可以通过在其中使用 console.log来确认这一点。由于目前的承诺没有拒绝处理程序,它也将被拒绝与拒绝值从以前的承诺和控制将被转移到下一个处理程序是 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中,您将获得被拒绝的 promise对象的值。

同样,由 catch处理程序1返回的承诺也被拒绝,同样的错误也拒绝了 promise,我们在第二个 catch处理程序中观察到了这个错误。

根据 规格(见3.III.d):

如果调用则抛出异常 e,
如果承诺或拒绝承诺已经被召唤,忽略它。
否则,以 e 为理由拒绝承诺。

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

我猜您想在代码中记录未处理的拒绝。大多数承诺库会为其激发一个 unhandledRejection。下面是 相关要点和它的讨论。

正如其他人所解释的那样,“黑洞”是因为扔进 .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!
});

当您希望链继续执行(尽管步骤失败)时,链中间的 catch 非常有用,但是在执行了信息日志记录或清除步骤之后,re-throw 对 继续失败非常有用,甚至可能更改抛出的错误。

特里克

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

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

甚至行号都保留了下来,所以 web 控制台中的链接直接将我带到发生(原始)错误的文件和行。

为什么会有用

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

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

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

...
throw new Error('My error message');
})
.catch(function (err) {
console.error(err.stack);
process.exit(0);
});

我尝试了上面详细介绍的 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;
});

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

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

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);
})

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

希望这有所帮助,这是我的第一个堆栈溢出的答案!

  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