在未处理的拒绝事件处理程序中获取堆栈跟踪

问题描述 投票:0回答:4

当我只使用

onunhandledrejection
处理程序捕获它时,如何确定 Promise 拒绝发生的位置?

console.error = ()=>{}
window.addEventListener('unhandledrejection', (promiseRejectionEvent) => {
  console.log('unhandled: ', Error().stack)
})

function main() {
  new Promise(() => { throw null })
}
main()

如果您在运行此命令后检查浏览器的控制台,您将看到如下内容:

sources tab console output

Error().stack
仅在其堆栈跟踪中包含拒绝处理函数本身(灰色输出
js:14:30
)。但是浏览器确实似乎知道拒绝发生在哪里:还有另一个红色错误输出(
Uncaught (in promise) null
),指向目标行(
js:18
)。我如何访问此线路信息?

似乎后者的输出是由浏览器的内部完成的,因为它不能像上面的例子那样通过覆盖

console.error
来防止。正如
MDN
所解释的那样,只能通过调用 promiseRejectionEvent.preventDefault() 来预防。但我不想阻止它,而是取而代之,例如用于记录目的。

现实世界的用例:当然可以不依赖

onunhandledrejection
事件处理程序,例如通过添加
.catch()
短语或至少抛出
throw new Error(null)
。但就我而言,我无法控制它,因为它是第三方代码。它今天意外地(可能是库错误)在客户端的浏览器中抛出,自动错误报告不包括堆栈跟踪。我试图缩小上面的潜在问题。谢谢!


编辑回应评论:

在 try/catch 中包装第三方代码? – weltschmerz

好点,但这无济于事,因为拒绝实际上发生在回调中:

window.addEventListener('unhandledrejection', (promiseRejectionEvent) => {
  console.log('unhandled: ', Error().stack) // <- stack once again does *not* include "main()", it is only printed out in the console
})

function main() {
  try {
    thirdPartyModule()
  } catch(e) {
    // Never caught
    console.log("caught:", e)
  }
}

// Example code
// We cannot change this function
function thirdPartyModule() {
  setTimeout(() =>
    new Promise(() =>
      { throw null }))
}

main()

javascript promise stack-trace
4个回答
2
投票

没有任何开箱即用的跟踪异步堆栈跟踪的好的解决方案,但可以使用 Zone.js 来做到这一点。如果您查看 Zone.js 页面上的演示,则有一个异步堆栈跟踪示例。

Zone 通过猴子修补所有创建异步任务的本机 API 来实现这一点。


2
投票

免责声明:使用此解决方法对调试很有用,但对性能有轻微的负面影响,并可能导致各种库行为异常;它不应该在生产代码中使用。


您可以将 Promise 构造函数替换为您自己的实现,其中包括创建它的堆栈跟踪。

window.Promise = class FAKEPROMISE extends Promise {
    constructor() {
        super(...arguments);
        this.__creationPoint = new Error().stack;
    }
};

这将为您提供创建任何给定承诺的点的堆栈跟踪。 请注意,

.then
.catch
.finally
都会创建新的承诺。

这不适用于由

async
函数创建的 Promises,因为它们不使用窗口的
Promise
构造函数。

这个可以通过阅读

promise
PromiseRejectionEvent
成员来使用:

window.addEventListener('unhandledrejection', (promiseRejectionEvent) => {
  console.log('unhandled: ', promiseRejectionEvent.promise.__creationPoint)
})

这将打印类似的东西:

unhandled:  Error
    at new FAKEPROMISE (script.js:4:32)
    at main (script.js:6:3)
    at script.js:9:1

1
投票

不可能

我想你想要的“堆栈跟踪”将包括带有

throw null;
的行,但是,当调用unhandledrejection
事件处理程序时,它不在堆栈中
。当
throw null;
被执行时,处理程序不会被直接(同步)调用,而是一个调用处理程序的微任务被排队。 (有关事件循环、任务和微任务的解释,请参阅 Jake Archibald“In The Loop”。)

这可以通过在抛出错误之前排队微任务来测试。如果 throwing 同步调用处理程序,微任务应该在它之后执行,但是如果 throw 队列中有一个调用处理程序的微任务,第一个微任务首先执行,然后是第二个微任务(调用处理程序)。

window.addEventListener('unhandledrejection', (promiseRejectionEvent) => {
  console.log('unhandled: ', Error().stack) // <- stack once again does *not* include "main()", it is only printed out in the console
})

function main() {
  try {
    thirdPartyModule()
  } catch (e) {
    // Never caught
    console.log("caught:", e)
  }
}

// Example code
// We cannot change this function
function thirdPartyModule() {
  setTimeout(() =>
    new Promise(() => {
      Promise.resolve().then(() => { // Queue a microtask before throwing
        console.log("Microtask")
      })
      throw null
    }))
}

main()

如您所见,我们的微任务首先执行,这意味着处理程序在微任务内部被调用。处理程序位于堆栈的顶部。


0
投票
process.on('unhandledRejection', (reason, promise) => {
    console.log('stackTrace:', reason.stack);
});

“原因”是一个本地错误对象。 您可以使用“stack”属性显示 stackTrace。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.