Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,13 @@ TraceSection s(
@try {
[inv invokeWithTarget:strongModule];
} @catch (NSException *exception) {
throw convertNSExceptionToJSError(runtime, exception, std::string{moduleName}, methodNameStr);
// Cannot rethrow C++ exceptions from async dispatch - nothing can catch them.
// Log the error for debugging purposes instead of crashing.
RCTLogError(
@"Exception thrown while invoking async method %s.%s: %@",
moduleName,
methodNameStr.c_str(),
exception);
Comment on lines +465 to +470
Copy link
Copy Markdown
Contributor

@RSNara RSNara Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Luxlorys, let's handle errors in async methods consistently.

Is there a strong reason for us to do something different in these changed lines vs here:

https://github.com/Luxlorys/react-native/blob/9f41b14761389b57f4947e89051a7865122ba4c6/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm#L399-L405

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 399-405 (Promise-based async methods) can re-throw NSException because they execute within the createPromise() wrapper infrastructure (lines 253-360).
When the exception is re-thrown at line 404, it's caught by the Promise wrapper which can then reject the Promise and communicate the error back to JavaScript
through the resolve/reject blocks.

Async void methods execute through a fundamentally different code path:

  1. They call performVoidMethodInvocation() which dispatches directly via invokeAsync() to a background queue
  2. There's no Promise wrapper, no resolve/reject blocks, no error communication channel
  3. The JS call has already returned undefined before the async work executes
  4. Any exception thrown (whether C++ or NSException) has nothing to catch it on the dispatch queue

If we used the same approach as line 404 (@throw exception;) for void methods, the app would still crash because there's no infrastructure to catch the
re-thrown NSException in the dispatch block's execution context.

Correct me if I missed something, I'm new here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RSNara Hey, check message above when you have time, please.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! So you're saying that we should be throwing in the promise case.

} @finally {
[retainedObjectsForInvocation removeAllObjects];
}
Expand Down