如何在Promise中捕获未捕获的异常

84

有没有一种方法可以全局捕获所有异常,包括Promise异常。例如:

    window.onerror = function myErrorHandler(errorMsg, url, lineNumber) {
        alert("Error occured: " + errorMsg);//or any message
        return false;
    }

    var myClass = function(){

    }


    var pr = new Promise(function(resolve, react){
        var myInstance = new myClass();
        myInstance.undefinedFunction(); // this will throw Exception
        resolve(myInstance);
    });


    pr.then(function(result){
        console.log(result);
    });

    // i know right will be this:
    // pr.then(function(result){
    //     console.log(result);
    // }).catch(function(e){
    //     console.log(e);
    // });

这个脚本会在不报错的情况下默默死掉。火狐浏览器调试工具中也看不到任何信息。

我的问题是,如果我犯了一个错误并忘记捕获它,是否有全局捕获的方法?

5个回答

49

更新:现在大多数浏览器中的本地 Promise 实现以下功能:

window.addEventListener("unhandledrejection", function(promiseRejectionEvent) { 
    // handle error here, for example log   
});
我们前几天刚讨论过这个问题。 以下是使用bluebird的方法:
window.onpossiblyunhandledexception = function(){
    window.onerror.apply(this, arguments); // call
}

window.onerror = function(err){
    console.log(err); // logs all errors
}

使用Bluebird,也可以使用Promise.onPossiblyUnhandledRejection。与Q不同,不需要调用done,因为库本身会检测未处理的拒绝(更新于2016年-现在我已经为Q编写了代码,并且它也做到了这一点)。

至于原生Promise-它们最终将向window.onerror或新处理程序报告,但规范流程尚未完成-您可以在此处跟踪它。


1
@JohnHatton 根据statcounter的数据,大多数浏览器都是Chrome。话虽如此-您始终可以使用Promise库来获得此行为。我已经联系了Firefox(在其标志下)Safari和Edge的开发人员,以便尽快在更多浏览器中实现此功能。 - Benjamin Gruenbaum
2
大多数浏览器都是Chrome。好吧,我想“在大多数会话中使用的浏览器”是“大多数浏览器”的一种定义……但不是我选择的定义。 - John Hatton
似乎最新版本的bluebird只需要基本上执行 window.addEventListener("unhandledrejection", function (event){}) 就行了。http://bluebirdjs.com/docs/api/error-management-configuration.html#global-rejection-events -- 我可能理解文档有误,但它听起来像是bluebird现在会为你触发一个 unhandledrejection 事件。 - Devin Rhode
@DavidSpector 这很不幸 - 提交一个 bug? - Benjamin Gruenbaum
是的,当我使用未处理拒绝处理程序时,它仅捕获控制台中显示的两个通知中的一个。 另一个可能是一个错误。 我应该在有更多时间时进行调查并与Chrome进行比较。 - David Spector
显示剩余3条评论

16

大多数Promise实现目前还没有提供您所需的功能,但是一些第三方Promise库(包括Qbluebird)提供了一个done()方法,可以捕获并重新抛出任何未捕获的错误,从而将它们输出到控制台。

编辑:请查看Benjamin Gruenbaum有关Bluebird处理未捕获异常的答案。)

因此,如果您使用了这个方法,只需要遵循一个经验法则,即您使用的任何Promise都应该返回或以.done()结束:

pr.then(function(result){
    console.log(result);
})
.done();

引用自 Q API 参考文档:

在使用 donethen 时的黄金法则是:要么将您的 Promise 返回给其他人,要么如果链以您结束,请调用 done 终止它。仅使用 catch 结束不足以确保安全,因为 catch 处理程序本身可能会抛出错误。

我意识到这仍然需要一些额外的工作,而且您仍然可能会忘记执行此操作,但与必须 .catch() 并明确处理每个错误相比,它是一个改进,尤其是对于上述原因。


2
实际上,done和简单地执行.catch(thrower)(或者使用快捷方式,例如添加Promise.prototype.done = ...)没有任何区别。因此,对于此目的而言,这是相当无意义的,特别是在Bluebird中,未捕获的错误通常会被报告。 - Esailija
1
@Esailija,你能详细说明一下吗?在这种情况下,“thrower”是什么? - JLRishe
function thrower(e){setTimeout(function(){throw e}, 0);} - Esailija
1
@Esailija 好的,也许在Bluebird中它是不必要的,但它仍然是防止错误在其他一些实现中被完全吞噬的最简单方法(据我所知),所以我不会说它是毫无意义的。 - JLRishe
1
@JLRishe 我认为他的观点是,人们不应该使用吞噬错误的 promise 实现(如 jQuery、Q、$q 等),而应该使用允许您跟踪未处理拒绝的 promise 库,例如 Bluebird、When、RSVP 等。 - Benjamin Gruenbaum
1
我所指的无意义是除了光荣的 catch settimeout throw 之外的独立概念。 - Esailija

6
在Node.js中,您可以使用:
process.on('unhandledRejection', (reason, promise) => {
  console.error(`Uncaught error in`, promise);
});

当你在未知的情况下进行调试时,这是非常有用的,但如果你知道哪个 Promise 并且可以访问它,你应该使用 .catch.done 来提高可维护性。 - Taku
在更新版本的Node中,这实际上已经成为了标准行为。因此,在未捕获的Promise错误将被记录,即使没有添加此代码片段。 - JussiR
你怎么能获取导致这个问题的 Promise 或者函数文件呢?问题在于 reason.stack 总是未定义。 - Loebre
使用 Promise (或 setTimeout)会创建一个新的调用栈,因此您无法看到 Promise 的调用位置。尽管 Chrome 的较新版本(V8?)允许您查看以前的调用栈,这样有助于调试异步代码。 - JussiR

2

如果你正在编写既可以在浏览器中执行,又可以在Node.js环境中执行的代码,你可以像下面这样将代码包装在一个自执行函数中:

var isNode = (typeof process !== 'undefined' && typeof process.on !== 'undefined');
(function(on, unhandledRejection) {
    // PUT ANY CODE HERE
    on(unhandledRejection, function(error, promise) {
        console.error(`Uncaught error in`, promise);
    });
    // PUT ANY CODE HERE
})(
    isNode ? process.on.bind(process) : window.addEventListener.bind(window),
    isNode ? "unhandledRejection" : "unhandledrejection"
);

如果您使用以下代码:

  • 在Node.js环境下运行,您的处理程序将附加到进程对象并在promise中有未捕获异常时执行。

  • 在浏览器环境下运行,您的处理程序将附加到窗口对象并在promise中有未捕获异常且您的浏览器支持unhandledrejection事件时执行。

  • 如果您在不支持unhandledrejection的浏览器环境中运行,则无法捕获未捕获的异常,您的unhandledrejection事件处理程序将永远不会被触发,但如果没有未捕获的异常,则不会出现任何错误。


0
如果您正在使用本地 Promise,那么它非常简单。
您只需要在某个地方使用.catch来捕获拒绝即可。
ajax(request).catch(function(rejected){
      console.log(rejected);
});

如果我没有在某个地方捕获它,未捕获的 Promise 将会一直显示。 但是如果我在某个地方捕获了它...


7
这并没有回答所询问的问题。 - csauve
即使你捕获了它,在控制台(Firefox)中仍会出现通知。 - David Spector

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接