Skip to content

Commit efbcb37

Browse files
fix(promise): Warn users about multiple versions of promise package which can cause unexpected behaviour (#3162)
Co-authored-by: LucasZF <[email protected]>
1 parent 248ea69 commit efbcb37

File tree

2 files changed

+40
-6
lines changed

2 files changed

+40
-6
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818

1919
- Use `android.namespace` for AGP 8 and RN 0.73 ([#3133](https://github.com/getsentry/sentry-react-native/pull/3133))
2020

21+
### Fixes
22+
23+
- Warn users about multiple versions of `promise` package which can cause unexpected behavior like undefined `Promise.allSettled` ([#3162](https://github.com/getsentry/sentry-react-native/pull/3162))
24+
2125
### Dependencies
2226

2327
- Bump JavaScript SDK from v7.54.0 to v7.57.0 ([#3119](https://github.com/getsentry/sentry-react-native/pull/3119), [#3153](https://github.com/getsentry/sentry-react-native/pull/3153))

src/js/integrations/reactnativeerrorhandlers.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,7 @@ export class ReactNativeErrorHandlers implements Integration {
7676
/* eslint-disable import/no-extraneous-dependencies,@typescript-eslint/no-var-requires */
7777
const { polyfillGlobal } = require('react-native/Libraries/Utilities/PolyfillFunctions');
7878

79-
// Below, we follow the exact way React Native initializes its promise library, and we globally replace it.
80-
const Promise = require('promise/setimmediate/es6-extensions');
79+
const Promise = this._getPromisePolyfill();
8180

8281
// As of RN 0.67 only done and finally are used
8382
require('promise/setimmediate/done');
@@ -86,6 +85,17 @@ export class ReactNativeErrorHandlers implements Integration {
8685
polyfillGlobal('Promise', () => Promise);
8786
/* eslint-enable import/no-extraneous-dependencies,@typescript-eslint/no-var-requires */
8887
}
88+
89+
/**
90+
* Single source of truth for the Promise implementation we want to use.
91+
* This is important for verifying that the rejected promise tracing will work as expected.
92+
*/
93+
private _getPromisePolyfill(): unknown {
94+
/* eslint-disable import/no-extraneous-dependencies,@typescript-eslint/no-var-requires */
95+
// Below, we follow the exact way React Native initializes its promise library, and we globally replace it.
96+
return require('promise/setimmediate/es6-extensions');
97+
}
98+
8999
/**
90100
* Attach the unhandled rejection handler
91101
*/
@@ -133,20 +143,40 @@ export class ReactNativeErrorHandlers implements Integration {
133143
*/
134144
private _checkPromiseAndWarn(): void {
135145
try {
146+
// `promise` package is a dependency of react-native, therefore it is always available.
147+
// but it is possible that the user has installed a different version of promise
148+
// or dependency that uses a different version.
149+
// We have to check if the React Native Promise and the `promise` package Promise are using the same reference.
150+
// If they are not, likely there are multiple versions of the `promise` package installed.
151+
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-extraneous-dependencies
152+
const ReactNativePromise = require('react-native/Libraries/Promise');
136153
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-extraneous-dependencies
137-
const Promise = require('promise/setimmediate/es6-extensions');
154+
const PromisePackagePromise = require('promise/setimmediate/es6-extensions');
155+
const UsedPromisePolyfill = this._getPromisePolyfill();
156+
157+
if (ReactNativePromise !== PromisePackagePromise) {
158+
logger.warn(
159+
'You appear to have multiple versions of the "promise" package installed. ' +
160+
'This may cause unexpected behavior like undefined `Promise.allSettled`. ' +
161+
'Please install the `promise` package manually using the exact version as the React Native package. ' +
162+
'See https://docs.sentry.io/platforms/react-native/troubleshooting/ for more details.',
163+
);
164+
}
138165

139-
if (Promise !== RN_GLOBAL_OBJ.Promise) {
166+
// This only make sense if the user disabled the integration Polyfill
167+
if (UsedPromisePolyfill !== RN_GLOBAL_OBJ.Promise) {
140168
logger.warn(
141-
'Unhandled promise rejections will not be caught by Sentry. Read about how to fix this on our troubleshooting page.',
169+
'Unhandled promise rejections will not be caught by Sentry. ' +
170+
'See https://docs.sentry.io/platforms/react-native/troubleshooting/ for more details.',
142171
);
143172
} else {
144173
logger.log('Unhandled promise rejections will be caught by Sentry.');
145174
}
146175
} catch (e) {
147176
// Do Nothing
148177
logger.warn(
149-
'Unhandled promise rejections will not be caught by Sentry. Read about how to fix this on our troubleshooting page.',
178+
'Unhandled promise rejections will not be caught by Sentry. ' +
179+
'See https://docs.sentry.io/platforms/react-native/troubleshooting/ for more details.',
150180
);
151181
}
152182
}

0 commit comments

Comments
 (0)