-
Notifications
You must be signed in to change notification settings - Fork 24.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(tm-android): Reject Promise if Turbo Module method throws an Error #37484
feat(tm-android): Reject Promise if Turbo Module method throws an Error #37484
Conversation
Base commit: 98e7ecd |
.../react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm
Outdated
Show resolved
Hide resolved
.../react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm
Outdated
Show resolved
Hide resolved
.../react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm
Outdated
Show resolved
Hide resolved
.../react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
I'm not 100% convinced we need to use the Error callback for this, given the overheads it requires. Consider the JS equivalent of this type of module invocation to be something like this, which also will not call the original reject handler.
My perspective is that the behaviour for all async native module calls in RN should be a Logbox in development (which we should fix if that's not happening here!) and a RN fatal error in production (which apps can still decide to handle by restarting the React app - without crashing the entire app). |
Making native exceptions more actionable in development by capturing a JS stacktrace to associate with it is a great idea by the way. I just don't think we want to do it in production - potentially we could make it a runtime flag so app authors can decide? |
On Android, we use ReactFeatureFlags - which you'll need to use JNI to access - see |
@javache Thank you for all the suggestions, corrections, and nits. I appreciate it. I've removed iOS changes from this PR making it Android only. I'll link PR with iOS changes here. Let me know if there is anything more! 🙏 iOS PR Here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it expected that even "regular" rejects (eg the module implementation explicitly rejecting the promise) including the original caller's JS stack? Does that align with behaviour we see in other JS engines?
...ges/react-native/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
I would say it's expected because to get the caller's JS stack the user had to enable the feature by the flag |
@javache I don't know how exactly to use the new AsyncCallback. I'm getting undefined in JS instead of the JSError. |
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
@javache Thanks for the notes, I made the updates, let me know what you think. 🙏 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great! I love how much more readable this has become (I'll review the iOS one once this is good-to-go)
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
...-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Outdated
Show resolved
Hide resolved
@javache has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
…or (facebook#37484) Summary: ### [iOS change here](facebook#40764) This PR builds upon the previous work done in facebook#36925, which introduced native stack traces to the JSError for synchronous functions. The current modifications concentrate on functions that return Promises. Prior to this PR, errors within Promise-returning functions would be thrown at the platform layer crashing the app without a link to the JS stack. After the implementation of this PR, errors thrown within Promise-returning functions are now captured and transformed into rejected Promises. These rejected Promises contain a JS Error object that contains both the JS stack trace and the cause, along with the platform stack trace. Additionally, this PR ensures that rejections from native functions are now linked to the JS stack trace, providing a more comprehensive view of the rejection flow. ## Changelog: <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> [GENERAL][ADDED] - Turbo Modules Promise-returning functions reject with JS and platform stack traces information Pull Request resolved: facebook#37484 Test Plan: | Android | |--------| | ![function_promise_android](https://github.com/krystofwoldrich/react-native/assets/31292499/1d1a3adf-986a-47b4-b98b-9e766176b7ae) | Example of intentionally rejected promise on Android: ``` { "name": "Error", "message": "Exception in HostFunction: intentional promise rejection", "stack": "[native code]\ntryCallTwo@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:25844:9\ndoResolve@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:25975:25\nPromise@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:25863:14\n[native code]\nrejectPromise@http://10.0.2.2:8081/js/examples/TurboModule/SampleTurboModuleExample.bundle?platform=android&lazy=true&app=com.facebook.react.uiapp&modulesOnly=true&dev=true&minify=false&runModule=true&shallow=true:42:70\nonPress@http://10.0.2.2:8081/js/examples/TurboModule/SampleTurboModuleExample.bundle?platform=android&lazy=true&app=com.facebook.react.uiapp&modulesOnly=true&dev=true&minify=false&runModule=true&shallow=true:242:71\n_performTransitionSideEffects@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:51896:22\n_receiveSignal@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:51852:45\nonResponderRelease@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:51715:34\ninvokeGuardedCallbackProd@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:2962:21\ninvokeGuardedCallback@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:3048:42\ninvokeGuardedCallbackAndCatchFirstError@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:3051:36\nexecuteDispatch@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:3115:48\nexecuteDispatchesInOrder@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:3132:26\nexecuteDispatchesAndRelease@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:4856:35\nforEach@[native code]\nforEachAccumulated@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:3574:22\nrunEventsInBatch@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:4874:27\nrunExtractedPluginEventsInBatch@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:4896:25\nhttp://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:4914:42\nbatchedUpdates$1@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:14750:20\nbatchedUpdates@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:4845:36\ndispatchEvent@http://10.0.2.2:8081/js/RNTesterApp.android.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.facebook.react.uiapp&modulesOnly=false&runModule=true:4907:23", "cause": { "nativeStackAndroid": [ { "lineNumber": 173, "file": "SampleTurboModule.java", "methodName": "getValueWithPromise", "class": "com.facebook.fbreact.specs.SampleTurboModule" }, { "lineNumber": -2, "file": "NativeRunnable.java", "methodName": "run", "class": "com.facebook.jni.NativeRunnable" }, { "lineNumber": 942, "file": "Handler.java", "methodName": "handleCallback", "class": "android.os.Handler" }, { "lineNumber": 99, "file": "Handler.java", "methodName": "dispatchMessage", "class": "android.os.Handler" }, { "lineNumber": 27, "file": "MessageQueueThreadHandler.java", "methodName": "dispatchMessage", "class": "com.facebook.react.bridge.queue.MessageQueueThreadHandler" }, { "lineNumber": 201, "file": "Looper.java", "methodName": "loopOnce", "class": "android.os.Looper" }, { "lineNumber": 288, "file": "Looper.java", "methodName": "loop", "class": "android.os.Looper" }, { "lineNumber": 228, "file": "MessageQueueThreadImpl.java", "methodName": "run", "class": "com.facebook.react.bridge.queue.MessageQueueThreadImpl$4" }, { "lineNumber": 1012, "file": "Thread.java", "methodName": "run", "class": "java.lang.Thread" } ], "userInfo": null, "message": "intentional promise rejection", "code": "code 1" } } ``` How I logged out the Errors: ```js console.log('Error in JS:', JSON.stringify({ name: e.name, message: e.message, stack: e.stack, ...e, }, null, 2)); ``` Reviewed By: RSNara Differential Revision: D50613349 Pulled By: javache fbshipit-source-id: b49c469118c8d8d27c43164f110dfe57ddd592d9
Summary:
iOS change here
This PR builds upon the previous work done in #36925, which introduced native stack traces to the JSError for synchronous functions.
The current modifications concentrate on functions that return Promises. Prior to this PR, errors within Promise-returning functions would be thrown at the platform layer crashing the app without a link to the JS stack.
After the implementation of this PR, errors thrown within Promise-returning functions are now captured and transformed into rejected Promises. These rejected Promises contain a JS Error object that contains both the JS stack trace and the cause, along with the platform stack trace.
Additionally, this PR ensures that rejections from native functions are now linked to the JS stack trace, providing a more comprehensive view of the rejection flow.
Changelog:
[GENERAL][ADDED] - Turbo Modules Promise-returning functions reject with JS and platform stack traces information
Test Plan:
Example of intentionally rejected promise on Android:
How I logged out the Errors: