Can Poyrazoğlu
Can Poyrazoğlu

Reputation: 34820

React Native find what caused Possible unhandled promise rejection

I haven't been paying attention to the logbox errors in my React Native app for a while as I was in a rush, now I just noticed that I have a warning every time I open my app, here is what I get when I tap it:

enter image description here

While I know this originates from something thrown in an async function or a rejected promise, I have so many things running when app launches and I have no idea where this warning is coming from. My app seems to be working normally and I've checked all my explicit throws and rejects from my code, added debugger statements to everywhere I called them, but no avail. As seen from the screenshot, the stack trace isn't useful at all either.

How can I find the cause of unhandled promise rejection in React Native?

Upvotes: 8

Views: 1565

Answers (2)

Ramakrishnan C S
Ramakrishnan C S

Reputation: 51

If your app uses Hermes, it's probably why you are not able to catch these unhandled promises as Hermes internally does some custom Promise implementation that entirely skips blocking flows or just ignores the error, when you receive unhandled promise rejections in Prod builds.

Create a file setupHermesPromiseTracking and include it in the beginning of your index or app.tsx file (entry point file).

*/utils/setupHermesPromiseRejectionTracking.ts

declare const HermesInternal:
  | {
      hasPromise?: () => boolean
      enablePromiseRejectionTracker?: (
        options: PromiseRejectionTrackingOptions
      ) => void
    }
  | undefined

// https://github.com/facebook/react-native/blob/1954f166c9f2b22e1f3bff1a3cecddc15f9e7c31/packages/react-native/Libraries/Core/polyfillPromise.js#L25
if (HermesInternal?.hasPromise?.() === true) {
  let defaultOptions: PromiseRejectionTrackingOptions | undefined
  if (__DEV__) {
    defaultOptions = defaultPromiseRejectionTrackingOptions
  }

  HermesInternal.enablePromiseRejectionTracker?.({
    allRejections: true,
    onUnhandled: (id, rejection) => {
      defaultOptions?.onUnhandled(id, rejection)
      // do what you want with the unhandled rejection
      // eg: datadog.captureException(rejection)
    },
    onHandled: (id) => {
      defaultOptions?.onHandled(id)
      console.warn(`Promise rejection handled (id: ${id})`)
    }
  })
}

export default () => {
  console.log("Hermes Promise Rejection Tracking initialized")
}

You can test it this way

// imports
import setupHermesPromiseRejectionTracking from "~/utils/setupHermesPromiseRejectionTracking"

setupHermesPromiseRejectionTracking()
const App = () => {
  const testUnhandledAsyncFn = async () => {
     throw new Error("This is an unhandled rejection")
  }

  useEffect(() => {
    void testUnhandledAsyncFn() // fire the fn with no try catch
  }

  return (
     <View>
      <Text> Hello World </Text>
    </View>
  );
}

Upvotes: 1

Ivan
Ivan

Reputation: 804

In cases like this you'd rather use a library that can help you get a longer and more detailed stack trace to pin point the issue. Check out this Stackoverflow question to find some examples. In my experience Bluebird works great with react native and can produce a more extensive and helpful stack trace.

// App.tsx 

import Promise from 'bluebird';

if (__DEV__) {
    // replace global promise with Bluebird
    global.Promise = Promise;
}

Upvotes: 2

Related Questions