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

dem*_*n85 119 javascript asynchronous throw promise es6-promise

为什么我不能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
});
Run Code Online (Sandbox Code Playgroud)

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

jib*_*jib 149

正如其他人所解释的那样,"黑洞"是因为投入内部是.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!
});
Run Code Online (Sandbox Code Playgroud)

当你希望链条尽管步骤失败时,链条中间的一个捕获是有用的,但重新抛出对于在记录信息或清理步骤之后继续失败很有用,甚至可能改变哪个错误被扔了.

特技

为了使错误在Web控制台中显示为错误,就像您最初的预期一样,我使用了这个技巧:

.catch(function(err) { setTimeout(function() { throw err; }); });
Run Code Online (Sandbox Code Playgroud)

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

为什么会这样

被称为promise履行或拒绝处理程序的函数中的任何异常都会自动转换为拒绝您应该返回的promise.调用您的函数的promise代码负责这一点.

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

  • 关于这个诀窍:你因为想要登录而投掷,所以为什么不直接登录呢?这个技巧将在"随机"时间抛出一个无法捕获的错误....但是异常的整个想法(以及promises处理它们的方式)是让*调用者负责*捕获错误并处理它.此代码有效地使调用者无法处理错误.为什么不做一个功能来为你处理它?`function logErrors(e){console.error(e)}`然后使用它像`do1().然后(do2).catch(logErrors)`.答案本身很棒btw,+ 1 (8认同)
  • Jib,这是一个有趣的技巧,你能帮助我理解为什么会有效吗? (3认同)
  • @StijndeWitt 就我而言,我试图在 `window.onerror` 事件处理程序中将错误详细信息发送到我的服务器。只有通过“setTimeout”技巧才能做到这一点。否则 `window.onerror` 永远不会听到关于 Promise 中发生的错误的消息。 (3认同)
  • @jib我正在编写一个AWS lambda,其中包含许多与此情况大致相似的promise.要在发生错误时利用AWS警报和通知,我需要让lambda崩溃抛出一个错误(我猜).诀窍是获得这个的唯一方法吗? (2认同)

the*_*eye 43

这里要了解的重要事项

  1. 无论是thencatch函数返回新承诺的对象.

  2. 投掷或明确拒绝,将当前承诺移至被拒绝的状态.

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

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

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


在你的情况下,你do1通过投掷一个Error对象来拒绝内部.现在,当前的承诺将处于拒绝状态,控制权将转移到下一个处理程序,这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);
});
Run Code Online (Sandbox Code Playgroud)

您将获得的输出将是类似的

Promise Promise { <rejected> [Error: do1] }
Promise1 Promise { <rejected> [Error: do1] }
Run Code Online (Sandbox Code Playgroud)

catch处理程序1中,您将promise对象的值视为已拒绝.

同样的方式,catch处理程序1 返回的promise 也promise被拒绝,并且被拒绝的错误与我们在第二个catch处理程序中观察它一样.

  • 也许值得补充一点,`.then()`处理程序是异步的(堆栈在执行之前被解开),因此它们内部的异常必须变成拒绝,否则就不会有异常处理程序来捕获它们. (3认同)

Rig*_*eek 7

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

.catch(function(err) { setTimeout(function() { throw err; }); });
Run Code Online (Sandbox Code Playgroud)

恼人的是,我发现这是完全无法测试的。因为它抛出一个异步错误,所以你不能把它包装在一个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;
});
Run Code Online (Sandbox Code Playgroud)

  • Jest 有 [timer mocks](https://facebook.github.io/jest/docs/timer-mocks.html) 可以处理这种情况。 (3认同)