mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
794d2264f9
Summary: As mentioned [here](https://github.com/react-native-community/react-native-releases/issues/34#issuecomment-417718601), Android is missing native Promise reject with a `userInfo` `WritableMap` support and also `nativeStack` support (which addresses `TODO(8850038)`). This PR adds Android support for both of these. React Native on iOS ([here](https://github.com/facebook/react-native/blob/master/React/Base/RCTUtils.m#L433)) and Windows ([here](https://github.com/Microsoft/react-native-windows/pull/732)) already support this so this is a relatively minor addition to bring Android in line with the other platforms. (JS support is also [here](https://github.com/facebook/react-native/blob/master/Libraries/BatchedBridge/NativeModules.js#L145-L148)) Existing methods remain unchanged other than general cleanup of variable names (`e -> throwable`) and adding code comments/docs. Additionally, the `ShareTestModule` implementation of Promise (SimplePromise) was updated to reflect these changes - other line changes in this file are from formatting in Android Studio - if this is an issue let me know. - Currently inconsistent with other platforms. - Blocking a couple of issues over at [invertase/react-native-firebase](https://github.com/invertase/react-native-firebase) - save for re-writing everything to Promise resolve only - which is no small change and isn't a great solution either. Pull Request resolved: https://github.com/facebook/react-native/pull/20940 Differential Revision: D13412527 Pulled By: cpojer fbshipit-source-id: 2ca6c5f3db9ff2c2986b02edda80bc73432f66d3
247 lines
7.6 KiB
Java
247 lines
7.6 KiB
Java
/**
|
|
* Copyright (c) Facebook, Inc. and its 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 javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
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 = 10;
|
|
|
|
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_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_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;
|
|
|
|
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(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, 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, 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, String message, 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, @Nonnull 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, 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, String message, @Nonnull 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) {
|
|
errorInfo.putString(ERROR_MAP_KEY_MESSAGE, throwable.getMessage());
|
|
} 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) {
|
|
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_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);
|
|
}
|
|
}
|