diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm index d33fd071d896..56a5ceb9ccd3 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm @@ -39,6 +39,16 @@ void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled) { RCTEnableTurboModule(turboModuleEnabled); +#ifndef RCT_TRACE_PROMISE_REJECTION_ENABLED +#if DEBUG +#define RCT_TRACE_PROMISE_REJECTION_ENABLED 1 +#endif +#endif + +#if RCT_TRACE_PROMISE_REJECTION_ENABLED + RCTEnableTraceTurboModulePromiseRejections(YES); +#endif + #if DEBUG // Disable idle timer in dev builds to avoid putting application in background and complicating // Metro reconnection logic. Users only need this when running the application using our CLI tooling. diff --git a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec index 71765def33b6..c95d78d3ad7c 100644 --- a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec +++ b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec @@ -16,6 +16,9 @@ else source[:tag] = "v#{version}" end +trace_promise_rejections_enabled = ENV["RCT_TRACE_PROMISE_REJECTION_ENABLED"] == "1" +trace_promise_rejections_flag = (trace_promise_rejections_enabled ? " -DRCT_TRACE_PROMISE_REJECTION_ENABLED" : "") + folly_config = get_folly_config() folly_compiler_flags = folly_config[:compiler_flags] folly_version = folly_config[:version] @@ -26,7 +29,7 @@ use_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == '1' new_arch_enabled_flag = (is_new_arch_enabled ? " -DRCT_NEW_ARCH_ENABLED" : "") is_fabric_enabled = true #is_new_arch_enabled || ENV["RCT_FABRIC_ENABLED"] hermes_flag = (use_hermes ? " -DUSE_HERMES" : "") -other_cflags = "$(inherited)" + folly_compiler_flags + new_arch_enabled_flag + hermes_flag +other_cflags = "$(inherited)" + folly_compiler_flags + new_arch_enabled_flag + hermes_flag + trace_promise_rejections_flag header_search_paths = [ "$(PODS_TARGET_SRCROOT)/../../ReactCommon", diff --git a/packages/react-native/React/Base/RCTBridge.h b/packages/react-native/React/Base/RCTBridge.h index 23937c185482..1a07c93b2381 100644 --- a/packages/react-native/React/Base/RCTBridge.h +++ b/packages/react-native/React/Base/RCTBridge.h @@ -77,6 +77,14 @@ void RCTSetTurboModuleInteropBridgeProxyLogLevel(RCTBridgeProxyLoggingLevel logL BOOL RCTTurboModuleInteropForAllTurboModulesEnabled(void); void RCTEnableTurboModuleInteropForAllTurboModules(BOOL enabled); +// Trace Rejected Promises of Turbo Modules (store callers' js stack) +RCT_EXTERN BOOL RCTTraceTurboModulePromiseRejections(void); +RCT_EXTERN void RCTEnableTraceTurboModulePromiseRejections(BOOL enabled); + +// Reject JS Promise on native throw in TM method call +RCT_EXTERN BOOL RCTRejectTurboModulePromiseOnNativeError(void); +RCT_EXTERN void RCTEnableRejectTurboModulePromiseOnNativeError(BOOL enabled); + typedef enum { kRCTGlobalScope, kRCTGlobalScopeUsingRetainJSCallback, diff --git a/packages/react-native/React/Base/RCTBridge.mm b/packages/react-native/React/Base/RCTBridge.mm index 8183fbb9d55e..2ccd76eae7e5 100644 --- a/packages/react-native/React/Base/RCTBridge.mm +++ b/packages/react-native/React/Base/RCTBridge.mm @@ -104,6 +104,28 @@ void RCTEnableTurboModule(BOOL enabled) turboModuleEnabled = enabled; } +static BOOL traceTurboModulePromiseRejections = NO; +BOOL RCTTraceTurboModulePromiseRejections(void) +{ + return traceTurboModulePromiseRejections; +} + +void RCTEnableTraceTurboModulePromiseRejections(BOOL enabled) +{ + traceTurboModulePromiseRejections = enabled; +} + +static BOOL rejectTurboModulePromiseOnNativeError = YES; +BOOL RCTRejectTurboModulePromiseOnNativeError(void) +{ + return rejectTurboModulePromiseOnNativeError; +} + +void RCTEnableRejectTurboModulePromiseOnNativeError(BOOL enabled) +{ + rejectTurboModulePromiseOnNativeError = enabled; +} + static BOOL turboModuleInteropEnabled = NO; BOOL RCTTurboModuleInteropEnabled(void) { diff --git a/packages/react-native/React/Base/RCTUtils.m b/packages/react-native/React/Base/RCTUtils.m index 190c06aab009..8d59f9417b8e 100644 --- a/packages/react-native/React/Base/RCTUtils.m +++ b/packages/react-native/React/Base/RCTUtils.m @@ -503,7 +503,7 @@ BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector) NSArray *stackTrace = [NSThread callStackSymbols]; NSMutableDictionary *userInfo; NSMutableDictionary *errorInfo = [NSMutableDictionary dictionaryWithObject:stackTrace - forKey:@"nativeStackIOS"]; + forKey:@"stackSymbols"]; if (error) { errorMessage = error.localizedDescription ?: @"Unknown error from a native module"; diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h index f54e1751fb94..46967862c6f2 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h @@ -152,7 +152,8 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule { bool isSync, const char *methodName, NSInvocation *inv, - NSMutableArray *retainedObjectsForInvocation); + NSMutableArray *retainedObjectsForInvocation, + _Nullable RCTPromiseRejectBlock reject); void performVoidMethodInvocation( jsi::Runtime &runtime, const char *methodName, diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm index 2678b19063e6..411da3d22b36 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm @@ -187,9 +187,14 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s throw std::runtime_error("Unsupported jsi::Value kind"); } -static jsi::Value createJSRuntimeError(jsi::Runtime &runtime, const std::string &message) -{ - return runtime.global().getPropertyAsFunction(runtime, "Error").call(runtime, message); +jsi::Value createJSRuntimeError(jsi::Runtime& runtime, jsi::Value&& message) { + return runtime.global() + .getPropertyAsFunction(runtime, "Error") + .call(runtime, std::move(message)); +} + +jsi::Value createJSRuntimeError(jsi::Runtime& runtime, const std::string& message) { + return createJSRuntimeError(runtime, jsi::String::createFromUtf8(runtime, message)); } /** @@ -208,17 +213,33 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s jsi::Value error = createJSRuntimeError(runtime, "Exception in HostFunction: " + reason); error.asObject(runtime).setProperty(runtime, "cause", std::move(cause)); - return {runtime, std::move(error)}; + return jsi::JSError(runtime, std::move(error)); +} + +/** + * Creates JSError with current JS runtime and NSDictionary data as cause. + */ +static jsi::JSError convertNSDictionaryToJSError(jsi::Runtime &runtime, NSDictionary *cause) +{ + jsi::Value error = createJSRuntimeError(runtime, std::string("Exception in HostFunction: ") + (cause[@"message"] ? [cause[@"message"] UTF8String] : "")); + error.asObject(runtime).setProperty(runtime, "cause", convertNSDictionaryToJSIObject(runtime, cause)); + return jsi::JSError(runtime, std::move(error)); } /** * Creates JS error value with current JS runtime and error details. */ -static jsi::Value convertJSErrorDetailsToJSRuntimeError(jsi::Runtime &runtime, NSDictionary *jsErrorDetails) +static jsi::Value convertJSErrorDetailsToJSRuntimeError(jsi::Runtime &runtime, NSDictionary *jsErrorDetails, const std::optional &jsInvocationStack) { NSString *message = jsErrorDetails[@"message"]; - auto jsError = createJSRuntimeError(runtime, [message UTF8String]); + auto jsError = createJSRuntimeError(runtime, std::string([message UTF8String])); + jsError.asObject(runtime).setProperty(runtime, "cause", convertObjCObjectToJSIValue(runtime, jsErrorDetails)); + + if (jsInvocationStack.has_value()) { + jsError.asObject(runtime).setProperty(runtime, "stack", *jsInvocationStack); + } + for (NSString *key in jsErrorDetails) { id value = jsErrorDetails[key]; jsError.asObject(runtime).setProperty(runtime, [key UTF8String], convertObjCObjectToJSIValue(runtime, value)); @@ -237,6 +258,16 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s jsi::Function Promise = runtime.global().getPropertyAsFunction(runtime, "Promise"); + // JS Stack at the time when the promise is created. + std::optional jsInvocationStack; + if (RCTTraceTurboModulePromiseRejections()) { + jsInvocationStack = createJSRuntimeError(runtime, jsi::Value::undefined()) + .asObject(runtime) + .getProperty(runtime, "stack") + .asString(runtime) + .utf8(runtime); + } + // Note: the passed invoke() block is not retained by default, so let's retain it here to help keep it longer. // Otherwise, there's a risk of it getting released before the promise function below executes. PromiseInvocationBlock invokeCopy = [invoke copy]; @@ -246,7 +277,7 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s runtime, jsi::PropNameID::forAscii(runtime, "fn"), 2, - [invokeCopy, jsInvoker = jsInvoker_, moduleName = name_, methodName]( + [invokeCopy, jsInvoker = jsInvoker_, moduleName = name_, methodName, jsInvocationStack = std::move(jsInvocationStack)]( jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) { std::string moduleMethod = moduleName + "." + methodName + "()"; @@ -296,8 +327,8 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s } NSDictionary *jsErrorDetails = RCTJSErrorFromCodeMessageAndNSError(code, message, error); - reject->call([jsErrorDetails](jsi::Runtime &rt, jsi::Function &jsFunction) { - jsFunction.call(rt, convertJSErrorDetailsToJSRuntimeError(rt, jsErrorDetails)); + reject->call([jsErrorDetails, jsInvocationStack](jsi::Runtime &rt, jsi::Function &jsFunction) { + jsFunction.call(rt, convertJSErrorDetailsToJSRuntimeError(rt, jsErrorDetails, jsInvocationStack)); }); resolveWasCalled = NO; resolve = std::nullopt; @@ -309,6 +340,38 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s })); } +static NSError * maybeCatchException( + BOOL shoudCatch, + id causeOrError) +{ + if (!shoudCatch) { + // Crash on native layer there is no promise in JS to reject. + // We executed JSFunction returning void asynchrounously. + throw; + } + + if ([causeOrError isKindOfClass:[NSError class]]) { + return causeOrError; + } + + if ([causeOrError isKindOfClass:[NSDictionary class]]) { + return [[NSError alloc] initWithDomain:RCTErrorDomain code:-1 userInfo:causeOrError]; + } + + if ([causeOrError isKindOfClass:[NSException class]]) { + NSException *exception = (NSException *)causeOrError; + return [[NSError alloc] initWithDomain:RCTErrorDomain code:-1 userInfo:@{ + @"name": exception.name, + NSLocalizedDescriptionKey: exception.reason, + @"stackSymbols": exception.callStackSymbols, + @"stackReturnAddresses": exception.callStackReturnAddresses, + }]; + } + + // This should never happen, to avoid consequent errors, we wrapp the unknown value in NSError. + return [[NSError alloc] initWithDomain:RCTErrorDomain code:-1 userInfo:@{ @"unknown": causeOrError }]; +} + /** * Perform method invocation on a specific queue as configured by the module class. * This serves as a backward-compatible support for RCTBridgeModule's methodQueue API. @@ -323,7 +386,8 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s bool isSync, const char *methodName, NSInvocation *inv, - NSMutableArray *retainedObjectsForInvocation) + NSMutableArray *retainedObjectsForInvocation, + _Nullable RCTPromiseRejectBlock reject) { __block id result; __weak id weakModule = instance_; @@ -343,12 +407,46 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s TurboModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodNameStr.c_str(), asyncCallCounter); } - @try { - [inv invokeWithTarget:strongModule]; - } @catch (NSException *exception) { - throw convertNSExceptionToJSError(runtime, exception); - } @finally { - [retainedObjectsForInvocation removeAllObjects]; + NSError *caughtException = nil; + BOOL shouldCatchException = isSync || reject; + try { + @try { + [inv invokeWithTarget:strongModule]; + } @catch (NSException *exception) { + caughtException = maybeCatchException(shouldCatchException, exception); + } @catch (NSError *error) { + caughtException = maybeCatchException(shouldCatchException, error); + } @catch (NSString *errorMessage) { + caughtException = maybeCatchException(shouldCatchException, @{ + NSLocalizedDescriptionKey: errorMessage, + }); + } @catch (id e) { + caughtException = maybeCatchException(shouldCatchException, @{ + NSLocalizedDescriptionKey: @"Unknown Objective-C Object thrown.", + }); + } @finally { + [retainedObjectsForInvocation removeAllObjects]; + } + } catch (const std::exception &exception) { + caughtException = maybeCatchException(shouldCatchException, @{ + NSLocalizedDescriptionKey: [NSString stringWithUTF8String:exception.what()], + }); + } catch (const std::string &errorMessage) { + caughtException = maybeCatchException(shouldCatchException, @{ + NSLocalizedDescriptionKey: [NSString stringWithUTF8String:errorMessage.c_str()], + }); + } catch (...) { + caughtException = maybeCatchException(shouldCatchException, @{ + NSLocalizedDescriptionKey: @"Unknown C++ exception thrown.", + }); + } + + if (caughtException) { + if (isSync) { + throw convertNSDictionaryToJSError(runtime, RCTJSErrorFromCodeMessageAndNSError(nil, nil, caughtException)); + } else { + reject(nil, nil, caughtException); + } } if (!isSync) { @@ -741,8 +839,17 @@ SystraceSection s( [inv setArgument:(void *)&rejectCopy atIndex:count + 3]; [retainedObjectsForInvocation addObject:resolveCopy]; [retainedObjectsForInvocation addObject:rejectCopy]; + RCTPromiseRejectBlock rejectOnNativeError = nil; + if (RCTRejectTurboModulePromiseOnNativeError()) { + rejectOnNativeError = rejectCopy; + }; // The return type becomes void in the ObjC side. - performMethodInvocation(runtime, isSyncInvocation, methodName, inv, retainedObjectsForInvocation); + performMethodInvocation(runtime, + isMethodSync(VoidKind), + methodName, + inv, + retainedObjectsForInvocation, + rejectOnNativeError); }); break; } @@ -763,7 +870,7 @@ SystraceSection s( case ObjectKind: case ArrayKind: case FunctionKind: { - id result = performMethodInvocation(runtime, true, methodName, inv, retainedObjectsForInvocation); + id result = performMethodInvocation(runtime, true, methodName, inv, retainedObjectsForInvocation, nil); TurboModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodName); returnValue = convertReturnIdToJSIValue(runtime, methodName, returnType, result); TurboModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName); diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.h b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.h index e37e275e1eba..c9412205604c 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.h +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.h @@ -38,6 +38,7 @@ - (void)voidFuncAssert; - (NSDictionary *)getObjectAssert:(NSDictionary *)arg; - (void)promiseAssert:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject; +- (NSDictionary *)getObjectCppThrows:(NSDictionary *)arg; - (NSDictionary *)constantsToExport; - (NSDictionary *)getConstants; diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.mm b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.mm index 5ad04fa341f4..7e850b4e904f 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.mm @@ -200,6 +200,16 @@ .invokeObjCMethod(rt, ObjectKind, "getConstants", @selector(getConstants), args, count); } +static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectCppThrows( + facebook::jsi::Runtime &rt, + TurboModule &turboModule, + const facebook::jsi::Value *args, + size_t count) +{ + return static_cast(turboModule) + .invokeObjCMethod(rt, ObjectKind, "getObjectCppThrows", @selector(getObjectCppThrows:), args, count); +} + NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms) : ObjCTurboModule(params) { @@ -223,6 +233,7 @@ methodMap_["voidFuncAssert"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert}; methodMap_["getObjectAssert"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert}; methodMap_["promiseAssert"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert}; + methodMap_["getObjectCppThrows"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectCppThrows}; methodMap_["getConstants"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants}; eventEmitterMap_["onPress"] = std::make_shared>(); eventEmitterMap_["onClick"] = std::make_shared>(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleLegacyModule.mm b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleLegacyModule.mm index 5613f858cf4d..8549faa5a8f8 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleLegacyModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleLegacyModule.mm @@ -200,6 +200,11 @@ - (NSDictionary *)constantsToExport @throw myException; } +RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectCppThrows : (NSDictionary *)arg) +{ + throw std::runtime_error("Intentional error from Cpp getObjectCppThrows"); +} + RCT_EXPORT_METHOD(voidFuncAssert) { RCTAssert(false, @"Intentional assert from ObjC voidFuncAssert"); diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm index 2013c70a344f..a2339bcdf610 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm @@ -166,7 +166,7 @@ - (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime RCT_EXPORT_METHOD(voidFuncThrows) { - NSException *myException = [NSException exceptionWithName:@"Excepption" + NSException *myException = [NSException exceptionWithName:@"Exception" reason:@"Intentional exception from ObjC voidFuncThrows" userInfo:nil]; @throw myException; @@ -174,7 +174,7 @@ - (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectThrows : (NSDictionary *)arg) { - NSException *myException = [NSException exceptionWithName:@"Excepption" + NSException *myException = [NSException exceptionWithName:@"Exception" reason:@"Intentional exception from ObjC getObjectThrows" userInfo:nil]; @throw myException; @@ -182,12 +182,17 @@ - (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime RCT_EXPORT_METHOD(promiseThrows : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) { - NSException *myException = [NSException exceptionWithName:@"Excepption" + NSException *myException = [NSException exceptionWithName:@"Exception" reason:@"Intentional exception from ObjC promiseThrows" userInfo:nil]; @throw myException; } +RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectCppThrows : (NSDictionary *)arg) +{ + throw std::runtime_error("Intentional error from Cpp getObjectCppThrows"); +} + RCT_EXPORT_METHOD(voidFuncAssert) { RCTAssert(false, @"Intentional assert from ObjC voidFuncAssert"); diff --git a/packages/react-native/src/private/specs/modules/NativeSampleTurboModule.js b/packages/react-native/src/private/specs/modules/NativeSampleTurboModule.js index 0cecad05b3db..b0f94a001be8 100644 --- a/packages/react-native/src/private/specs/modules/NativeSampleTurboModule.js +++ b/packages/react-native/src/private/specs/modules/NativeSampleTurboModule.js @@ -59,6 +59,7 @@ export interface Spec extends TurboModule { +voidFuncAssert?: () => void; +getObjectAssert?: (arg: Object) => Object; +promiseAssert?: () => Promise; + +getObjectCppThrows?: (arg: Object) => Object; } export default (TurboModuleRegistry.getEnforcing( diff --git a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js index 2d590df3907a..34a67640c629 100644 --- a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js +++ b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js @@ -65,7 +65,8 @@ type ErrorExamples = | 'promiseThrows' | 'voidFuncAssert' | 'getObjectAssert' - | 'promiseAssert'; + | 'promiseAssert' + | 'getObjectCppThrows'; class SampleTurboModuleExample extends React.Component<{||}, State> { static contextType: React$Context = RootTagContext; @@ -91,6 +92,7 @@ class SampleTurboModuleExample extends React.Component<{||}, State> { .then(() => {}) .catch(e => { this._setResult('rejectPromise', e.message); + console.error('Error:', e, 'Stack: ', e.stack, 'Cause: ', e.cause); }), getConstants: () => NativeSampleTurboModule.getConstants(), voidFunc: () => NativeSampleTurboModule.voidFunc(), @@ -122,7 +124,7 @@ class SampleTurboModuleExample extends React.Component<{||}, State> { try { NativeSampleTurboModule.voidFuncThrows?.(); } catch (e) { - console.error(e); + console.error('Error:', e, 'Stack: ', e.stack, 'Cause: ', e.cause); return e.message; } }, @@ -130,7 +132,7 @@ class SampleTurboModuleExample extends React.Component<{||}, State> { try { NativeSampleTurboModule.getObjectThrows?.({a: 1, b: 'foo', c: null}); } catch (e) { - console.error(e); + console.error('Error:', e, 'Stack: ', e.stack, 'Cause: ', e.cause); return e.message; } }, @@ -138,14 +140,15 @@ class SampleTurboModuleExample extends React.Component<{||}, State> { NativeSampleTurboModule.promiseThrows?.() .then(() => {}) .catch(e => { - console.error(e); + this._setResult('promiseThrows', e.message); + console.error('Error:', e, 'Stack: ', e.stack, 'Cause: ', e.cause); }); }, voidFuncAssert: () => { try { NativeSampleTurboModule.voidFuncAssert?.(); } catch (e) { - console.error(e); + console.error('Error:', e, 'Stack: ', e.stack, 'Cause: ', e.cause); return e.message; } }, @@ -153,7 +156,7 @@ class SampleTurboModuleExample extends React.Component<{||}, State> { try { NativeSampleTurboModule.getObjectAssert?.({a: 1, b: 'foo', c: null}); } catch (e) { - console.error(e); + console.error('Error:', e, 'Stack: ', e.stack, 'Cause: ', e.cause); return e.message; } }, @@ -161,9 +164,18 @@ class SampleTurboModuleExample extends React.Component<{||}, State> { NativeSampleTurboModule.promiseAssert?.() .then(() => {}) .catch(e => { - console.error(e); + this._setResult('promiseAssert', e.message); + console.error('Error:', e, 'Stack: ', e.stack, 'Cause: ', e.cause); }); }, + getObjectCppThrows: () => { + try { + NativeSampleTurboModule.getObjectCppThrows?.({a: 1, b: 'foo', c: null}); + } catch (e) { + console.error('Error:', e, 'Stack: ', e.stack, 'Cause: ', e.cause); + return e.message; + } + }, installJSIBindings: () => { return global.__SampleTurboModuleJSIBindings; },