sentry-javascript: Support for "Unhandled promise rejection" / "Uncaught (in promise)"

Example use case (JSBin):

var p = new Promise(function(resolve, reject) {
    window.setTimeout(
        function() {
            reject();
        }, 10);
});

p.then(function () {
    console.log('done!');
});

If the promise throws an exception, and there’s no error handler, the browser will spit out an uncaught promise error.

image

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 18
  • Comments: 41 (19 by maintainers)

Commits related to this issue

Most upvoted comments

what’s the TL;DR solution here?

@joaocunha – looking at the code above, it looks like you could do the following as a workaround:

window.onunhandledrejection = function(data) {
    Raven.captureException(data.reason);
}

Just a heads up for anyone coming across this thread. There is a bug in Chrome 51 and higher (as recent as Chrome 56) where unhandled promise rejections are never fired on localhost. If you’re testing locally then you may never see this event fired!

See: https://bugs.chromium.org/p/v8/issues/detail?id=4874

I’m using Babel (which uses core-js), to get it to work there I’ve had to:

  1. delete window.Promise at the very start of the app, before loading any other code to ensure core-js’s Promise polyfill is used instead
  2. register a handler as window.onunhandledrejection = .. instead of window.addEventListener('unhandledrejection', ..)

My handler looks like:

window.onunhandledrejection = e => {
  console.warn('Unhandled Promise Rejection', e.reason)
  Raven.captureException(e.reason, {
    extra: { unhandledPromise: true }
  })
}

And to delete the native Promise, I’ve got a file src/disableNativePromises.js

if (window.Promise) {
  console.debug(`This browser provides a native Promise. I'm disabling it to force use of core-js to allow "onunhandledrejection".`)
  delete window.Promise
}

and I’m loading that as the first file in my entry config for Webpack:

module.exports  = {
  entry: [
    './src/disableNativePromises',
    'babel-polyfill',
    './src/index'
  ],
  // ...
}

Just updated the issue above about Chrome. It doesn’t work for me in Chrome 56 if webpack devtool option is set to eval, which was the default setting for local development.

Oddly enough, I see the rejection on raven.js:278 in the wrapped function.

@jessepollak – Raven.js wraps async methods in try/catch for uncaught exceptions (literally throw X), but that does not cover uncaught promises. The stack trace via the console still shows the wrap, though.

what’s the TL;DR solution here?

@MarkMurphy – We’re going to make it a config option soon-ish, but in the meantime you can implement yourself pretty easily:

https://docs.sentry.io/clients/javascript/usage/#promises

Just a note. If you are using Redux-Thunk with async/await, you can catch these unhandled promise rejections by catching redux-thunk errors manually.

code here https://gist.github.com/akhayoon/24be6ca9634d228893b8c4bd79f0f33c

https://github.com/zloirock/core-js/issues/140 was resolved, though I wouldn’t close this issue until a suitable workaround can be automated. awesome project!

@joaomilho – So, I played around with this, and the thing is, reason is non-standard and could contain anything. It could be a string, it could be an error, etc.

The examples from MDN actually demonstrate passing both an error and a string: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject

The problem here is that Raven.captureException purely captures Error objects, and Raven.captureMessage captures strings.

Perhaps the best solution is:

window.addEventListener('unhandledrejection', (err)  => {
  var reason = err.reason;
  if (err instanceof Error) {
    Raven.captureException(reason);
  } else if (Object.prototype.toString.call(reason) === '[object String]') {
    Raven.captureMessage(reason);
  }
});

We could also introduce a function, say, Raven.capture that tries to call the appropriate function based on the type of the passed arguments.

Edit: @mattrobenolt points out that Raven.captureException automatically falls back to messages, so this if statement is not necessary. Just use captureException.

@joaocunha – so @mattrobenolt and I have had a discussion about this. It seems like React your module loader has wrapped loading the module in a try/catch, and instead of re-throwing the ReferenceError, it passes it down via the Promise rejection. Because the error isn’t actually thrown, our onerror handler doesn’t catch it and we instead get this Unhandled promise.

So … we feel this might be a React module-loader-specific thing. We’re going to dig further into React, make sure we have a good handle on this, then get back to you.

If anyone has similar examples re: uncaught promise rejections, we’d love to hear 'em.