Files
react-native/packages/react-native/Libraries/promiseRejectionTrackingOptions.js
T
Krystof WoldrichandFacebook GitHub Bot c4082c9ce2 surface uncaught promise rejections with ExceptionsManager.handleException instead of swallowing them (#51594)
Summary:
This PR fixes logging of rejections, which stopped working with the transition from using `LogBox` for warnings to directing users to use React Native Debugger, where warnings can be viewed in the `Console` tab.

Rejected promises before this PR used `LogBox.addLog` with the `level: warn` directly without `console.warn`, which notified users to open debugger to view a warning (after rejected promise) without actually printing any warnings to the console.

This PR uses `ExceptionsManager.handleException(error, false /* isFatal */);` for promise rejections that correctly raising an error to the console if needed.

### Rejected Promise in Chrome
Displaying the error as long as it's uncaught in the form:
`Uncaught (in promise) Error`
and if there's a message in that error:
`Uncaught (in promise) Error: ${message}`
once the error is handled, this error disappears:
{F1978806409}
This is done with `Runtime.exceptionThrown`, and  `Runtime.exceptionRevoked`:
{F1979815326}

## Changelog:
[General][Breaking] unhandled promises are now handled by ExceptionsManager.handleException, instead of being swallowed as Logbox Warnings.
----
This is a breaking change because we changed the wording for these errors and because some systems (like Sentry) might start monitoring a new set of errors when this code is adopted.

Pull Request resolved: https://github.com/facebook/react-native/pull/51594

Test Plan:
Use `Promise.reject` and observe that the rejection is now shown symbolicated in LogBox and also printed in React Native Debugger Console.

```
<Button
  title="Uncaught Promise Rejection"
  onPress={() => {
    const err = new Error('test error');
    err.cause = new Error('cause');
    const promise = Promise.resolve().then(() => {
      throw err;
      // also test with throwing a string / object:
      // throw 'aa';
      // throw {test: 'object'};
      // return Promise.reject();
    });

    setTimeout(() => {
      promise.catch(e => {
        console.log('promise error caught', e);
      });
    }, 4000);
  }}
/>
```

### Before
There's no indication whatsoever there's an uncaught promise.

### After
An error is logged:
Error-
{F1979749366}
Text-
{F1979749350}
Object-
{F1979749351}
Promise.reject() or undefined-
 {F1979749826}

Reviewed By: hoxyq

Differential Revision: D75442651

Pulled By: vzaidman

fbshipit-source-id: bf6c56e643f03997f5a604b85c543aad62648a29
2025-07-17 07:55:50 -07:00

59 lines
1.6 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
import typeof {enable} from 'promise/setimmediate/rejection-tracking';
import ExceptionsManager from './Core/ExceptionsManager';
let rejectionTrackingOptions: $NonMaybeType<Parameters<enable>[0]> = {
allRejections: true,
onUnhandled: (id, rejection) => {
let message: string;
if (rejection === undefined) {
message = '';
} else if (
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
Object.prototype.toString.call(rejection) === '[object Error]'
) {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
message = Error.prototype.toString.call(rejection);
} else {
try {
message = require('pretty-format').format(rejection);
} catch {
message =
typeof rejection === 'string'
? rejection
: JSON.stringify((rejection: $FlowFixMe));
}
}
ExceptionsManager.handleException(
new Error(
`Uncaught (in promise, id: ${id})${message ? `: "${message}"` : ''}`,
{
cause: rejection,
},
),
false /* isFatal */,
);
},
onHandled: id => {
const warning =
`Promise rejection handled (id: ${id})\n` +
'This means you can ignore any previous messages of the form ' +
`"Uncaught (in promise, id: ${id})"`;
console.warn(warning);
},
};
export default rejectionTrackingOptions;