/* * 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 @DoNotStrip constructor(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 { callback -> callback.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) } private 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" } }