在unhandledrejection事件处理程序中获取堆栈跟踪

发布时间:2020-07-06 23:36

当我仅使用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()

回答1

目前还没有很好的解决方案来跟踪异步堆栈跟踪,但是可以使用Zone.js进行跟踪。如果您在Zone.js页面上查看演示,则有一个异步堆栈跟踪的示例。

Zone通过猴子修补所有创建异步任务以实现此目的的本机API来工作。

回答2

不可能。

我想象您想要的“堆栈跟踪”将包含带有throw null;的行,但是,当调用unhandledrejection事件处理程序时,不在堆栈中。执行throw null;时,不直接(同步)调用该处理程序,而是将一个调用该处理程序的微任务排队。 (有关事件循环,任务和微任务的说明,请参见"In The Loop" by Jake Archibald。)

可以通过在引发错误之前对微任务排队来测试。如果throwing同步调用处理程序,则微任务应在其后执行,但是如果throwing将调用该处理程序的微任务排队,则第一个微任务先执行,然后执行第二个(调用处理程序)。

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()