[Bridge] Add support for JS async functions to RCT_EXPORT_METHOD

Summary:
Adds support for JS async methods and helps guide people writing native modules w.r.t. the callbacks. With this diff, on the native side you write:

```objc
RCT_EXPORT_METHOD(getValueAsync:(NSString *)key
                       resolver:(RCTPromiseResolver)resolve
                       rejecter:(RCTPromiseRejecter)reject)
{
  NSError *error = nil;
  id value = [_nativeDataStore valueForKey:key error:&error];

  // "resolve" and "reject" are automatically defined blocks that take
  // any object (nil is OK) and an NSError, respectively
  if (!error) {
    resolve(value);
  } else {
    reject(error);
  }
}
```

On the JS side, you can write:

```js
var {DemoDataStore} = require('react-native').NativeModules;
DemoDataStore.getValueAsync('sample-key').then((value) => {
  console.log('Got:', value);
}, (error) => {
  console.error(error);
  // "error" is an Error object whose message is the NSError's description.
  // The NSError's code and domain are also set, and the native trace i
Closes https://github.com/facebook/react-native/pull/1232
Github Author: James Ide <ide@jameside.com>

Test Plan: Imported from GitHub, without a `Test Plan:` line.
This commit is contained in:
James Ide
2015-06-09 14:26:40 -07:00
parent cf8c2693af
commit 90439cec26
4 changed files with 190 additions and 42 deletions

View File

@@ -19,9 +19,17 @@ var slice = Array.prototype.slice;
var MethodTypes = keyMirror({
remote: null,
remoteAsync: null,
local: null,
});
type ErrorData = {
message: string;
domain: string;
code: number;
nativeStackIOS?: string;
};
/**
* Creates remotely invokable modules.
*/
@@ -36,21 +44,40 @@ var BatchedBridgeFactory = {
*/
_createBridgedModule: function(messageQueue, moduleConfig, moduleName) {
var remoteModule = mapObject(moduleConfig.methods, function(methodConfig, memberName) {
return methodConfig.type === MethodTypes.local ? null : function() {
var lastArg = arguments.length > 0 ? arguments[arguments.length - 1] : null;
var secondLastArg = arguments.length > 1 ? arguments[arguments.length - 2] : null;
var hasSuccCB = typeof lastArg === 'function';
var hasErrorCB = typeof secondLastArg === 'function';
hasErrorCB && invariant(
hasSuccCB,
'Cannot have a non-function arg after a function arg.'
);
var numCBs = (hasSuccCB ? 1 : 0) + (hasErrorCB ? 1 : 0);
var args = slice.call(arguments, 0, arguments.length - numCBs);
var onSucc = hasSuccCB ? lastArg : null;
var onFail = hasErrorCB ? secondLastArg : null;
return messageQueue.call(moduleName, memberName, args, onFail, onSucc);
};
switch (methodConfig.type) {
case MethodTypes.remote:
return function() {
var lastArg = arguments.length > 0 ? arguments[arguments.length - 1] : null;
var secondLastArg = arguments.length > 1 ? arguments[arguments.length - 2] : null;
var hasErrorCB = typeof lastArg === 'function';
var hasSuccCB = typeof secondLastArg === 'function';
hasSuccCB && invariant(
hasErrorCB,
'Cannot have a non-function arg after a function arg.'
);
var numCBs = (hasSuccCB ? 1 : 0) + (hasErrorCB ? 1 : 0);
var args = slice.call(arguments, 0, arguments.length - numCBs);
var onSucc = hasSuccCB ? secondLastArg : null;
var onFail = hasErrorCB ? lastArg : null;
messageQueue.call(moduleName, memberName, args, onSucc, onFail);
};
case MethodTypes.remoteAsync:
return function(...args) {
return new Promise((resolve, reject) => {
messageQueue.call(moduleName, memberName, args, resolve, (errorData) => {
var error = _createErrorFromErrorData(errorData);
reject(error);
});
});
};
case MethodTypes.local:
return null;
default:
throw new Error('Unknown bridge method type: ' + methodConfig.type);
}
});
for (var constName in moduleConfig.constants) {
warning(!remoteModule[constName], 'saw constant and method named %s', constName);
@@ -59,7 +86,6 @@ var BatchedBridgeFactory = {
return remoteModule;
},
create: function(MessageQueue, modulesConfig, localModulesConfig) {
var messageQueue = new MessageQueue(modulesConfig, localModulesConfig);
return {
@@ -80,4 +106,14 @@ var BatchedBridgeFactory = {
}
};
function _createErrorFromErrorData(errorData: ErrorData): Error {
var {
message,
...extraErrorInfo,
} = errorData;
var error = new Error(message);
error.framesToPop = 1;
return Object.assign(error, extraErrorInfo);
}
module.exports = BatchedBridgeFactory;