reactotron
reactotron copied to clipboard
Unhandled promise rejections
Problem
Async error reporting is an awful dev experience.
We don't get a red box, we get a yellow box with a string representation of the stack trace.
So let's pipe unhandled promise rejections into Reactotron as if they were normal synchronous errors.
Overview
Looks like there's a few ways to do this. Each more ghetto than the next. I'm ok with this.
Since 0.44 of React Native, they started tracking unhandled exceptions as a yellow box. In their promise
module, they've got some hooks to inject in your own tracking. And this is what React Native does.
Option 1
This is how React Native gets their yellow box on the screen. In the onUnhandled
, they do some pretty printing and display the error. Which is pretty much never what I personally want to see.
To me, an unhandled promise rejection is an error. Full stop.
In this implementation, we just report the error (which runs the source map lookup gauntlet).
if (__DEV__) {
require("promise/setimmediate/rejection-tracking").enable({
allRejections: true,
onUnhandled: (id, error = {}) => {
console.tron.reportError(error)
},
onHandled: () => {},
})
}
Option 2
The problem with the first option is that it replaces the yellow box behaviour entirely. I'm not sure I'm cool with that. We don't want to diverge too far from what RN devs expect.
This implementation will do both. It'll log the error with Reactotron and still put up the yellow box. We just swizzle good ol' _87
. 🤦♂️
A nice side-effect of this is that we don't have to wait for the hardcoded 2 second delay on most errors that flow thru unhandled promises.
Also, I'm not sure if error trackers like mobile center and crashlytics hook these same things, but if they do, this is the safer of the two options.
if (__DEV__) {
const old87 = Promise._87
const new87 = (promise, err) => {
console.tron.reportError(err)
old87(promise, err)
}
Promise._87 = new87
}
Option 3
There might be other ways to do this. I'm open to suggestions.
For example, we might be able to swizzle console.warn
and parse it like that? Seems like it might be a bit fragile because we'd have to do some string parsing to filter unhandled rejections.
Where To Put This
Probably right inside the track error handling function in reactotron-react-native
I'd reckon.
We could likely remove the if (__DEV__)
parts since that should already be protected up at the app. Reactotron shouldn't be installed in production mode unless folks really want to. In which case, they know what they're doing.
so, where do we put this code?
if (__DEV__) {
require("promise/setimmediate/rejection-tracking").enable({
allRejections: true,
onUnhandled: (id, error = {}) => {
console.tron.reportError(error)
},
onHandled: () => {},
})
}
I would love to use this too! I tried Option 1 and 2 above in several different places within my RN 0.59 app with no luck. My onUnhandled()
function never gets called when there's an unhandled promise rejection.
I placed my code on my App.js
, the component called by the index.js
, outside the component and it really helps debugging!
Guys, I just wanna say thanks a million for figuring out how this works internally :) I just placed the code at the top of my index.js and it works like a charm! (RN 61 btw). Definitely would love to see this incorporated in Reactotron, but for now riding steadily on the snippet 👍
Hey everyone, we just upgraded to RN 0.63.2 and this snippet stopped working, I've tried debugging it but sadly with no success, does anyone by chance know how to solve this? I'll report if I learn more
Hi all, @jjercx @JakeStoeffler @skellock, it's been a while but I believe I have found the solution to enabling this again, at least in RN 63+. In my setup, there are two issues: first of all, onUnhandled is never fired. Secondly, reportError doesn't actually report the error.
I've found this library https://github.com/iyegoroff/react-native-promise-rejection-utils, which does some more horrible internal reaching, but following his example, the onUnhandled does fire. I do not exactly know why his solution works and this one doesn't anymore, but hey, it works.
The solution for the second problem is rather easy, I copied over reactotron's code from the trackGlobalErrors plugin and had to make two little changes (see commit https://github.com/abeltje1/reactotron-react-native/commit/5fe3ecc815abd79ed7d29ff63ffe53ac64646465). I've tested it in RN 0.64.2 on iOS only, with and without Hermes enabled. Together it currently looks like this:
import {getUnhandledPromiseRejectionTracker, setUnhandledPromiseRejectionTracker} from 'react-native-promise-rejection-utils'
const prevTracker = getUnhandledPromiseRejectionTracker()
let symbolicateStackTrace
let parseErrorStack
const reportPromises = () => {
setUnhandledPromiseRejectionTracker((id, error) => {
parseErrorStack = parseErrorStack || require('react-native/Libraries/Core/Devtools/parseErrorStack')
symbolicateStackTrace = symbolicateStackTrace || require('react-native/Libraries/Core/Devtools/symbolicateStackTrace')
const parsedStacktrace = parseErrorStack(error.stack)
symbolicateStackTrace(parsedStacktrace).then(goodStack => {
let stack = goodStack.stack.map(stackFrame => ({
fileName: stackFrame.file,
functionName: stackFrame.methodName,
lineNumber: stackFrame.lineNumber,
}))
console.tron.error(error.message, stack)
})
})
}
export default reportPromises
running this function once on startup of your app should report errors to Reactotron again! :)
Enjoy!
Cheers,
Abel
This is great, thanks! This should definitely be incorporated into the reactotron-react-native repo, but it looks basically unmaintained at this point... last time a commit was made that wasn't a dependency bump or one-off single line tweak (and even those there are only three) was over two years ago now.
Hey thanks! I'm amazed it still works without issues haha. Yeah it's too bad reactotron-rn seems kinda dead, let's hope somebody picks it up again (or maybe we should do it ourselves..) :)
Hey thanks! I'm amazed it still works without issues haha. Yeah it's too bad reactotron-rn seems kinda dead, let's hope somebody picks it up again (or maybe we should do it ourselves..) :)
I mean fyi I haven't actually tested it, just came across the issue when wondering about implementing it, and thought it looked about right haha
Embarrassed to say this, as CTO of the company that put out Reactotron, but I ran into an unhandled promise rejection in a RN app and Googled lots of solutions and eventually landed on ... this.
Yes, this should definitely be in reactotron-react-native. I'll push to get it included, but since the attention of our primary maintainers of Reactotron is elsewhere, it could be a bit. But I need it now, so.... 😅
Can confirm this indeed works. Will try to get this fixed soon.
Hey @jamonholmgren, that's great news (and a bit funny)! I've been steadily riding on this script for more than a year now, but would love to see it included so I can remove the horrible code from my own codebase :). One question, if you'd like to answer: what's the current priority of the maintainers of Reactotron?