为什么我不能扔进Promise.catch处理程序?

时间:2015-06-08 17:20:19

标签: javascript asynchronous promise throw es6-promise

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

6 个答案:

答案 0 :(得分:142)

正如其他人所解释的那样,“黑洞”是因为在.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控制台中的链接会直接指向发生(原始)错误的文件和行。

为什么会起作用

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

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

答案 1 :(得分:40)

要理解的重要事项

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

  2. 无论是投掷还是明确拒绝,都会将当前承诺移至被拒绝的状态。

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

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

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

  6. 在您的情况下,您通过抛出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中,您将promise对象的值视为已拒绝。

    同样地,catch处理程序1返回的promise也被拒绝,但promise被拒绝的同一错误,我们在第二个catch处理程序中观察它

答案 2 :(得分:3)

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

答案 3 :(得分:2)

根据the spec (see 3.III.d)

  

d。如果调用则抛出异常e,
  一个。如果已调用resolvePromise或rejectPromise,请忽略它。
  湾否则,以e为理由拒绝承诺。

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

的捷径

我想您想在代码中记录未处理的拒绝。大多数promises库为它激发unhandledRejection。以下是relevant gist的讨论。

答案 4 :(得分:1)

我知道这有点晚了,但是我遇到了这个线程,而且所有解决方案都不容易实现,所以我想出了自己的方法:

我添加了一个小助手函数,该函数返回一个承诺,就像这样:

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

然后,如果我在任何promise链中都有一个特定的地方要抛出一个错误(并拒绝promise),那么我只需从上面的函数中返回构造的错误,就像这样:

template <typename T>
struct List{

   struct Node{
      T data;
      Node *next;
   };

   // add, remove etc...

   Node *locate(const T &a) const{
      for(Node *node = head; node; node = node->next)
         if (node->data == a)
            return node;

      return nullptr;
   }

private;
   Node *head;
};

通过这种方式,我可以控制从promise链内部抛出额外的错误。如果您还想处理“正常”承诺错误,则可以扩展捕获范围以分别处理“自我抛出”错误。

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

答案 5 :(得分:0)

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

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