最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Get stack trace inside unhandledrejection event handler - Stack Overflow

programmeradmin3浏览0评论

How can I determine where the Promise rejection happened when I only caught it using onunhandledrejection handler?

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

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

How can I determine where the Promise rejection happened when I only caught it using onunhandledrejection handler?

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

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

If you check your browser's console after running this, you will see something like:

The Error().stack only includes the rejection handler function itself in its stack trace (grey output js:14:30). But the browser does seem to know where the rejection happened: There is another red error output (Uncaught (in promise) null), pointing to the target line (js:18). How can I access this line information?

It seems that the latter output is being done by the browser's internals, as it is not preventable by overwriting console.error like in the example above. It is only preventable by calling promiseRejectionEvent.preventDefault(), as explained on MDN. But I don't want to prevent it anyway, but retrieve it instead, for example for logging purposes.

Real world use case: It would of course be possible to not rely on onunhandledrejection event handler, e.g. by adding a .catch() phrase or at least throwing throw new Error(null). But in my case, I have no control over it as it is third party code. It threw unexpectedly today (probably a library bug) at a client's browser and the automatic error report did not include a stack trace. I tried to narrow down the underlying issue above. Thanks!


Edit in response to ments:

Wrap the third party code in a try/catch? – weltschmerz

Good point, but this does not help because the rejection actually happens inside a callback:

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

Share Improve this question edited Aug 2, 2020 at 16:34 phil294 asked Jul 6, 2020 at 23:36 phil294phil294 10.9k8 gold badges72 silver badges107 bronze badges 2
  • Wrap the third party code in a try/catch? – Dennis Hackethal Commented Aug 2, 2020 at 4:40
  • @weltschmerz Yes, but the third party code does not actually return a Promise, so it cant be caught. I updated the answer with an example. Thanks – phil294 Commented Aug 2, 2020 at 16:36
Add a ment  | 

4 Answers 4

Reset to default 5

Disclaimer: Using this workaround is useful for debugging, but has minor negative performance implications, and could cause various libraries to misbehave; it should not be used in production code.


You can replace the Promise constructor with your own implementation that includes a stack trace for where it was created.

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

This will give you a stack trace of the point where any given promise was created. Note that .then, .catch, and .finally all create new promises.

This will not function with Promises created by async functions, as those do not use the window's Promise constructor.

This can be used by reading the promise member of the PromiseRejectionEvent:

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

Which will print something like:

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

There is no any good solution to track async stack trace out of the box, but it's possible to do using Zone.js. If you check out the demo on Zone.js page, there is example of an async stack trace.

Zone works by monkey patching all native API's that create async tasks to achieve that.

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

"reason" is a native Error object. You can display the stackTrace with the "stack" property.

It is not possible.

I imagine the "stack trace" you want would include the line with throw null;, however, that's not in the stack when the unhandledrejection event handler is called. When throw null; is executed, the handler is not called directly (synchronously), but instead a microtask that calls the handler is queued. (For an explanation of the event loop, tasks and microtasks, see "In The Loop" by Jake Archibald.)

This can be tested by queuing a microtask right before throwing the error. If throwing calls the handler synchronously, the microtask should execute after it, but if throwing queues a microtask that calls the handler, the first microtask executes first, and then the second one (that calls the handler).

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

As you can see, our microtask executed first, so that means the handler is called inside a microtask. The handler is at the top of the stack.

发布评论

评论列表(0)

  1. 暂无评论