【问题标题】:Get stack trace inside unhandledrejection event handler在 unhandledrejection 事件处理程序中获取堆栈跟踪
【发布时间】:2020-08-03 22:01:50
【问题描述】:

当我仅使用 onunhandledrejection 处理程序捕获 Promise 拒绝时,如何确定它发生在哪里?

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

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

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

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

似乎后者的输出是由浏览器的内部完成的,因为它不能通过覆盖console.error 来防止,就像上面的例子一样。只能通过调用promiseRejectionEvent.preventDefault() 来预防,如MDN 中所述。但我不想阻止它,而是取而代之,例如用于记录目的。

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


编辑以响应 cmets:

将第三方代码包装在 try/catch 中? – 世界观

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

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

【问题讨论】:

  • 将第三方代码包装在 try/catch 中?
  • @weltschmerz 是的,但是第三方代码实际上并没有返回一个 Promise,所以它不能被捕获。我用一个例子更新了答案。谢谢

标签: javascript promise stack-trace


【解决方案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()

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

    【讨论】:

    • 你说的有道理,但我不认为这是不可能的:在执行代码示例后检查你的浏览器控制台(不是来自 SO 的控制台),并展开红色的“未捕获(承诺) 空”消息。您将看到一个指向new Promise(() =&gt; { 行的漂亮堆栈跟踪,但我似乎无法以某种方式以编程方式获取此信息。它只在控制台中。
    • “我想你想要的“堆栈跟踪”将包括带有 throw null; 的行 - 不,我实际上是指(至少)其中带有 main() 的行。对不起,如果这不够清楚。但无论如何这并不重要,至少 一些 有用的堆栈跟踪行会很棒。在所有当前示例中,unhandledrejection 事件处理程序毫无用处。
    • 不幸的是,这也是不可能的。您只能获取到处理程序的堆栈跟踪。我不会说unhandledrejection 事件没有用。所有的承诺错误都应该是.catched,就像同步错误一样。 unhandledrejection 事件实际上不应该触发。
    猜你喜欢
    • 1970-01-01
    • 2013-03-08
    • 2010-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多