Migrate PromiseImpl to Kotlin (#51241)

Summary:
Migrate com.facebook.react.bridge.PromiseImpl to Kotlin.

## Changelog:

[INTERNAL] - Migrate com.facebook.react.bridge.PromiseImpl to Kotlin

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

Test Plan:
```bash
yarn test-android
yarn android
```

Reviewed By: rshest

Differential Revision: D74567102

Pulled By: cortinico

fbshipit-source-id: 1a7a36a88c0499e5bc35dc37d276219aaf823c90
This commit is contained in:
Mateo Guzmán
2025-05-12 04:48:28 -07:00
committed by Facebook GitHub Bot
parent a12d9f9d33
commit 25abb0acdf
3 changed files with 243 additions and 258 deletions
@@ -1032,7 +1032,8 @@ public abstract interface class com/facebook/react/bridge/Promise {
public abstract fun resolve (Ljava/lang/Object;)V
}
public class com/facebook/react/bridge/PromiseImpl : com/facebook/react/bridge/Promise {
public final class com/facebook/react/bridge/PromiseImpl : com/facebook/react/bridge/Promise {
public static final field Companion Lcom/facebook/react/bridge/PromiseImpl$Companion;
public fun <init> (Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V
public fun reject (Ljava/lang/String;)V
public fun reject (Ljava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
@@ -1047,6 +1048,9 @@ public class com/facebook/react/bridge/PromiseImpl : com/facebook/react/bridge/P
public fun resolve (Ljava/lang/Object;)V
}
public final class com/facebook/react/bridge/PromiseImpl$Companion {
}
public abstract class com/facebook/react/bridge/ReactApplicationContext : com/facebook/react/bridge/ReactContext {
public fun <init> (Landroid/content/Context;)V
}
@@ -1,257 +0,0 @@
/*
* 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.
*/
package com.facebook.react.bridge;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.proguard.annotations.DoNotStrip;
/*
* Implementation of {@link Promise} that represents a JavaScript Promise which can be passed to the
* native module as a method parameter.
*
* Methods annotated with {@link ReactMethod} that use a {@link Promise} as the last parameter
* will be marked as "promise" and will return a promise when invoked from JavaScript.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
@DoNotStrip
public class PromiseImpl implements Promise {
// Number of stack frames to parse and return to mReject.invoke
// for ERROR_MAP_KEY_NATIVE_STACK
private static final int ERROR_STACK_FRAME_LIMIT = 50;
private static final String ERROR_DEFAULT_CODE = "EUNSPECIFIED";
private static final String ERROR_DEFAULT_MESSAGE = "Error not specified.";
// Keys for mReject's WritableMap
private static final String ERROR_MAP_KEY_CODE = "code";
private static final String ERROR_MAP_KEY_MESSAGE = "message";
private static final String ERROR_MAP_KEY_NAME = "name";
private static final String ERROR_MAP_KEY_USER_INFO = "userInfo";
private static final String ERROR_MAP_KEY_NATIVE_STACK = "nativeStackAndroid";
// Keys for ERROR_MAP_KEY_NATIVE_STACK's StackFrame maps
private static final String STACK_FRAME_KEY_CLASS = "class";
private static final String STACK_FRAME_KEY_FILE = "file";
private static final String STACK_FRAME_KEY_LINE_NUMBER = "lineNumber";
private static final String STACK_FRAME_KEY_METHOD_NAME = "methodName";
private @Nullable Callback mResolve;
private @Nullable Callback mReject;
@DoNotStrip
public PromiseImpl(@Nullable Callback resolve, @Nullable Callback reject) {
mResolve = resolve;
mReject = reject;
}
/**
* Successfully resolve the Promise with an optional value.
*
* @param value Object
*/
@Override
public void resolve(@Nullable Object value) {
if (mResolve != null) {
mResolve.invoke(value);
mResolve = null;
mReject = null;
}
}
/**
* Report an error without an exception using a custom code and error message.
*
* @param code String
* @param message String
*/
@Override
public void reject(String code, @Nullable String message) {
reject(code, message, /*Throwable*/ null, /*WritableMap*/ null);
}
/**
* Report an exception with a custom code.
*
* @param code String
* @param throwable Throwable
*/
@Override
public void reject(String code, @Nullable Throwable throwable) {
reject(code, /*Message*/ null, throwable, /*WritableMap*/ null);
}
/**
* Report an exception with a custom code and error message.
*
* @param code String
* @param message String
* @param throwable Throwable
*/
@Override
public void reject(String code, @Nullable String message, @Nullable Throwable throwable) {
reject(code, message, throwable, /*WritableMap*/ null);
}
/**
* Report an exception, with default error code. Useful in catch-all scenarios where it's unclear
* why the error occurred.
*
* @param throwable Throwable
*/
@Override
public void reject(Throwable throwable) {
reject(/*Code*/ null, /*Message*/ null, throwable, /*WritableMap*/ null);
}
/* ---------------------------
* With userInfo WritableMap
* --------------------------- */
/**
* Report an exception, with default error code, with userInfo. Useful in catch-all scenarios
* where it's unclear why the error occurred.
*
* @param throwable Throwable
* @param userInfo WritableMap
*/
@Override
public void reject(Throwable throwable, WritableMap userInfo) {
reject(/*Code*/ null, /*Message*/ null, throwable, userInfo);
}
/**
* Reject with a code and userInfo WritableMap.
*
* @param code String
* @param userInfo WritableMap
*/
@Override
public void reject(String code, WritableMap userInfo) {
reject(code, /*Message*/ null, /*Throwable*/ null, userInfo);
}
/**
* Report an exception with a custom code and userInfo.
*
* @param code String
* @param throwable Throwable
* @param userInfo WritableMap
*/
@Override
public void reject(String code, @Nullable Throwable throwable, WritableMap userInfo) {
reject(code, /*Message*/ null, throwable, userInfo);
}
/**
* Report an error with a custom code, error message and userInfo, an error not caused by an
* exception.
*
* @param code String
* @param message String
* @param userInfo WritableMap
*/
@Override
public void reject(String code, @Nullable String message, WritableMap userInfo) {
reject(code, message, /*Throwable*/ null, userInfo);
}
/**
* Report an exception with a custom code, error message and userInfo.
*
* @param code String
* @param message String
* @param throwable Throwable
* @param userInfo WritableMap
*/
@Override
public void reject(
@Nullable String code,
@Nullable String message,
@Nullable Throwable throwable,
@Nullable WritableMap userInfo) {
if (mReject == null) {
mResolve = null;
return;
}
WritableNativeMap errorInfo = new WritableNativeMap();
if (code == null) {
errorInfo.putString(ERROR_MAP_KEY_CODE, ERROR_DEFAULT_CODE);
} else {
errorInfo.putString(ERROR_MAP_KEY_CODE, code);
}
// Use the custom message if provided otherwise use the throwable message.
if (message != null) {
errorInfo.putString(ERROR_MAP_KEY_MESSAGE, message);
} else if (throwable != null) {
String throwableMessage = throwable.getMessage();
// Fallback to the trowable name, so we record some useful information
if (throwableMessage == null || throwableMessage.isEmpty()) {
throwableMessage = throwable.getClass().getCanonicalName();
}
errorInfo.putString(ERROR_MAP_KEY_MESSAGE, throwableMessage);
} else {
// The JavaScript side expects a map with at least an error message.
// /Libraries/BatchedBridge/NativeModules.js -> createErrorFromErrorData
// TYPE: (errorData: { message: string })
errorInfo.putString(ERROR_MAP_KEY_MESSAGE, ERROR_DEFAULT_MESSAGE);
}
// For consistency with iOS ensure userInfo key exists, even if we null it.
// iOS: /React/Base/RCTUtils.m -> RCTJSErrorFromCodeMessageAndNSError
if (userInfo != null) {
errorInfo.putMap(ERROR_MAP_KEY_USER_INFO, userInfo);
} else {
errorInfo.putNull(ERROR_MAP_KEY_USER_INFO);
}
// Attach a nativeStackAndroid array if a throwable was passed
// this matches iOS behavior - iOS adds a `nativeStackIOS` property
// iOS: /React/Base/RCTUtils.m -> RCTJSErrorFromCodeMessageAndNSError
if (throwable != null) {
errorInfo.putString(ERROR_MAP_KEY_NAME, throwable.getClass().getCanonicalName());
StackTraceElement[] stackTrace = throwable.getStackTrace();
WritableNativeArray nativeStackAndroid = new WritableNativeArray();
// Build an an Array of StackFrames to match JavaScript:
// iOS: /Libraries/Core/Devtools/parseErrorStack.js -> StackFrame
for (int i = 0; i < stackTrace.length && i < ERROR_STACK_FRAME_LIMIT; i++) {
StackTraceElement frame = stackTrace[i];
WritableMap frameMap = new WritableNativeMap();
// NOTE: no column number exists StackTraceElement
frameMap.putString(STACK_FRAME_KEY_CLASS, frame.getClassName());
frameMap.putString(STACK_FRAME_KEY_FILE, frame.getFileName());
frameMap.putInt(STACK_FRAME_KEY_LINE_NUMBER, frame.getLineNumber());
frameMap.putString(STACK_FRAME_KEY_METHOD_NAME, frame.getMethodName());
nativeStackAndroid.pushMap(frameMap);
}
errorInfo.putArray(ERROR_MAP_KEY_NATIVE_STACK, nativeStackAndroid);
} else {
errorInfo.putArray(ERROR_MAP_KEY_NATIVE_STACK, new WritableNativeArray());
}
mReject.invoke(errorInfo);
mResolve = null;
mReject = null;
}
/* ------------
* Deprecated
* ------------ */
@Override
@Deprecated
public void reject(String message) {
reject(/*Code*/ null, message, /*Throwable*/ null, /*WritableMap*/ null);
}
}
@@ -0,0 +1,238 @@
/*
* 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.
*/
package com.facebook.react.bridge
import com.facebook.proguard.annotations.DoNotStrip
/**
* Implementation of [Promise] that represents a JavaScript Promise which can be passed to the
* native module as a method parameter. Methods annotated with [ReactMethod] that use a [Promise] as
* the last parameter will be marked as "promise" and will return a promise when invoked from
* JavaScript.
*/
@DoNotStrip
public class PromiseImpl(private var resolve: Callback?, private var reject: Callback?) : Promise {
/**
* Successfully resolve the [Promise] with an optional value.
*
* @param value Object
*/
override fun resolve(value: Any?) {
resolve?.let { res ->
res.invoke(value)
resolve = null
reject = null
}
}
/**
* Report an error without an exception using a custom code and error message.
*
* @param code String
* @param message String
*/
override fun reject(code: String, message: String?) {
reject(code, message, null, null)
}
/**
* Report an exception with a custom code.
*
* @param code String
* @param throwable Throwable
*/
override fun reject(code: String, throwable: Throwable?) {
reject(code, null, throwable, null)
}
/**
* Report an exception with a custom code and error message.
*
* @param code String
* @param message String
* @param throwable Throwable
*/
override fun reject(code: String, message: String?, throwable: Throwable?) {
reject(code, message, throwable, null)
}
/**
* Report an exception, with default error code. Useful in catch-all scenarios where it's unclear
* why the error occurred.
*
* @param throwable Throwable
*/
override fun reject(throwable: Throwable) {
reject(null, null, throwable, null)
}
/* ---------------------------
* With userInfo WritableMap
* --------------------------- */
/**
* Report an exception, with default error code, with userInfo. Useful in catch-all scenarios
* where it's unclear why the error occurred.
*
* @param throwable Throwable
* @param userInfo WritableMap
*/
override fun reject(throwable: Throwable, userInfo: WritableMap) {
reject(null, null, throwable, userInfo)
}
/**
* Reject with a code and userInfo WritableMap.
*
* @param code String
* @param userInfo WritableMap
*/
override fun reject(code: String, userInfo: WritableMap) {
reject(code, null, null, userInfo)
}
/**
* Report an exception with a custom code and userInfo.
*
* @param code String
* @param throwable Throwable
* @param userInfo WritableMap
*/
override fun reject(code: String, throwable: Throwable?, userInfo: WritableMap) {
reject(code, null, throwable, userInfo)
}
/**
* Report an error with a custom code, error message and userInfo, an error not caused by an
* exception.
*
* @param code String
* @param message String
* @param userInfo WritableMap
*/
override fun reject(code: String, message: String?, userInfo: WritableMap) {
reject(code, message, null, userInfo)
}
/**
* Report an exception with a custom code, error message and userInfo.
*
* @param code String
* @param message String
* @param throwable Throwable
* @param userInfo WritableMap
*/
override fun reject(
code: String?,
message: String?,
throwable: Throwable?,
userInfo: WritableMap?
) {
if (reject == null) {
resolve = null
return
}
val errorInfo = WritableNativeMap()
if (code == null) {
errorInfo.putString(ERROR_MAP_KEY_CODE, ERROR_DEFAULT_CODE)
} else {
errorInfo.putString(ERROR_MAP_KEY_CODE, code)
}
// Use the custom message if provided otherwise use the throwable message.
if (message != null) {
errorInfo.putString(ERROR_MAP_KEY_MESSAGE, message)
} else if (throwable != null) {
var throwableMessage = throwable.message
// Fallback to the throwable name, so we record some useful information
if (throwableMessage.isNullOrEmpty()) {
throwableMessage = throwable.javaClass.canonicalName
}
errorInfo.putString(ERROR_MAP_KEY_MESSAGE, throwableMessage)
} else {
// The JavaScript side expects a map with at least an error message.
// /Libraries/BatchedBridge/NativeModules.js -> createErrorFromErrorData
// TYPE: (errorData: { message: string })
errorInfo.putString(ERROR_MAP_KEY_MESSAGE, ERROR_DEFAULT_MESSAGE)
}
// For consistency with iOS ensure userInfo key exists, even if we null it.
// iOS: /React/Base/RCTUtils.m -> RCTJSErrorFromCodeMessageAndNSError
if (userInfo != null) {
errorInfo.putMap(ERROR_MAP_KEY_USER_INFO, userInfo)
} else {
errorInfo.putNull(ERROR_MAP_KEY_USER_INFO)
}
// Attach a nativeStackAndroid array if a throwable was passed
// this matches iOS behavior - iOS adds a `nativeStackIOS` property
// iOS: /React/Base/RCTUtils.m -> RCTJSErrorFromCodeMessageAndNSError
if (throwable != null) {
errorInfo.putString(ERROR_MAP_KEY_NAME, throwable.javaClass.canonicalName)
val stackTrace = throwable.stackTrace
val nativeStackAndroid = WritableNativeArray()
// Build an an Array of StackFrames to match JavaScript:
// iOS: /Libraries/Core/Devtools/parseErrorStack.js -> StackFrame
var i = 0
while (i < stackTrace.size && i < ERROR_STACK_FRAME_LIMIT) {
val frame = stackTrace[i]
val frameMap: WritableMap = WritableNativeMap()
// NOTE: no column number exists StackTraceElement
frameMap.putString(STACK_FRAME_KEY_CLASS, frame.className)
frameMap.putString(STACK_FRAME_KEY_FILE, frame.fileName)
frameMap.putInt(STACK_FRAME_KEY_LINE_NUMBER, frame.lineNumber)
frameMap.putString(STACK_FRAME_KEY_METHOD_NAME, frame.methodName)
nativeStackAndroid.pushMap(frameMap)
i++
}
errorInfo.putArray(ERROR_MAP_KEY_NATIVE_STACK, nativeStackAndroid)
} else {
errorInfo.putArray(ERROR_MAP_KEY_NATIVE_STACK, WritableNativeArray())
}
reject?.invoke(errorInfo)
resolve = null
reject = null
}
/* ------------
* Deprecated
* ------------ */
@Deprecated("Use reject(code, message) instead.", ReplaceWith("reject(code, message)"))
override fun reject(message: String) {
reject(null, message, null, null)
}
public companion object {
// Number of stack frames to parse and return to reject.invoke
// for ERROR_MAP_KEY_NATIVE_STACK
private const val ERROR_STACK_FRAME_LIMIT = 50
private const val ERROR_DEFAULT_CODE = "EUNSPECIFIED"
private const val ERROR_DEFAULT_MESSAGE = "Error not specified."
// Keys for reject's WritableMap
private const val ERROR_MAP_KEY_CODE = "code"
private const val ERROR_MAP_KEY_MESSAGE = "message"
private const val ERROR_MAP_KEY_NAME = "name"
private const val ERROR_MAP_KEY_USER_INFO = "userInfo"
private const val ERROR_MAP_KEY_NATIVE_STACK = "nativeStackAndroid"
// Keys for ERROR_MAP_KEY_NATIVE_STACK's StackFrame maps
private const val STACK_FRAME_KEY_CLASS = "class"
private const val STACK_FRAME_KEY_FILE = "file"
private const val STACK_FRAME_KEY_LINE_NUMBER = "lineNumber"
private const val STACK_FRAME_KEY_METHOD_NAME = "methodName"
}
}