mirror of
https://github.com/status-im/react-native.git
synced 2025-01-10 01:25:39 +00:00
c9a1956c4f
Summary:
public
In 9baff8f437 (diff-8d9841e5b53fd6c9cf3a7f431827e319R331)
, I incorrectly assumed that iOS was wrapping promises in an extra Array. What was really happening is that all the callers were doing this. I removed the wrapping in the callers and the special case handling MessageQueue.
Now one can pass whatever object one wants to resolve and it will show properly in the resolve call on the js side. This fixes issue https://github.com/facebook/react-native/issues/5851
Reviewed By: nicklockwood
Differential Revision: D2921565
fb-gh-sync-id: 9f81e2a87f6a48e9197413b843e452db345a7ff9
shipit-source-id: 9f81e2a87f6a48e9197413b843e452db345a7ff9
381 lines
11 KiB
JavaScript
381 lines
11 KiB
JavaScript
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule MessageQueue
|
|
*/
|
|
|
|
/*eslint no-bitwise: 0*/
|
|
|
|
'use strict';
|
|
|
|
let Systrace = require('Systrace');
|
|
let ErrorUtils = require('ErrorUtils');
|
|
let JSTimersExecution = require('JSTimersExecution');
|
|
let Platform = require('Platform');
|
|
|
|
let invariant = require('invariant');
|
|
let keyMirror = require('keyMirror');
|
|
let stringifySafe = require('stringifySafe');
|
|
|
|
let MODULE_IDS = 0;
|
|
let METHOD_IDS = 1;
|
|
let PARAMS = 2;
|
|
let CALL_IDS = 3;
|
|
let MIN_TIME_BETWEEN_FLUSHES_MS = 5;
|
|
|
|
let TRACE_TAG_REACT_APPS = 1 << 17;
|
|
|
|
let SPY_MODE = false;
|
|
|
|
let MethodTypes = keyMirror({
|
|
remote: null,
|
|
remoteAsync: null,
|
|
});
|
|
|
|
var guard = (fn) => {
|
|
try {
|
|
fn();
|
|
} catch (error) {
|
|
ErrorUtils.reportFatalError(error);
|
|
}
|
|
};
|
|
|
|
class MessageQueue {
|
|
|
|
constructor(remoteModules, localModules) {
|
|
this.RemoteModules = {};
|
|
|
|
this._callableModules = {};
|
|
this._queue = [[], [], [], 0];
|
|
this._moduleTable = {};
|
|
this._methodTable = {};
|
|
this._callbacks = [];
|
|
this._callbackID = 0;
|
|
this._callID = 0;
|
|
this._lastFlush = 0;
|
|
this._eventLoopStartTime = new Date().getTime();
|
|
|
|
[
|
|
'invokeCallbackAndReturnFlushedQueue',
|
|
'callFunctionReturnFlushedQueue',
|
|
'flushedQueue',
|
|
].forEach((fn) => this[fn] = this[fn].bind(this));
|
|
|
|
let modulesConfig = this._genModulesConfig(remoteModules);
|
|
this._genModules(modulesConfig);
|
|
localModules && this._genLookupTables(
|
|
this._genModulesConfig(localModules),this._moduleTable, this._methodTable
|
|
);
|
|
|
|
this._debugInfo = {};
|
|
this._remoteModuleTable = {};
|
|
this._remoteMethodTable = {};
|
|
this._genLookupTables(
|
|
modulesConfig, this._remoteModuleTable, this._remoteMethodTable
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Public APIs
|
|
*/
|
|
callFunctionReturnFlushedQueue(module, method, args) {
|
|
guard(() => {
|
|
this.__callFunction(module, method, args);
|
|
this.__callImmediates();
|
|
});
|
|
|
|
return this.flushedQueue();
|
|
}
|
|
|
|
invokeCallbackAndReturnFlushedQueue(cbID, args) {
|
|
guard(() => {
|
|
this.__invokeCallback(cbID, args);
|
|
this.__callImmediates();
|
|
});
|
|
|
|
return this.flushedQueue();
|
|
}
|
|
|
|
flushedQueue() {
|
|
this.__callImmediates();
|
|
|
|
let queue = this._queue;
|
|
this._queue = [[], [], [], this._callID];
|
|
return queue[0].length ? queue : null;
|
|
}
|
|
|
|
processModuleConfig(config, moduleID) {
|
|
const module = this._genModule(config, moduleID);
|
|
this._genLookup(config, moduleID, this._remoteModuleTable, this._remoteMethodTable);
|
|
return module;
|
|
}
|
|
|
|
getEventLoopRunningTime() {
|
|
return new Date().getTime() - this._eventLoopStartTime;
|
|
}
|
|
|
|
/**
|
|
* "Private" methods
|
|
*/
|
|
|
|
__callImmediates() {
|
|
Systrace.beginEvent('JSTimersExecution.callImmediates()');
|
|
guard(() => JSTimersExecution.callImmediates());
|
|
Systrace.endEvent();
|
|
}
|
|
|
|
__nativeCall(module, method, params, onFail, onSucc) {
|
|
if (onFail || onSucc) {
|
|
// eventually delete old debug info
|
|
(this._callbackID > (1 << 5)) &&
|
|
(this._debugInfo[this._callbackID >> 5] = null);
|
|
|
|
this._debugInfo[this._callbackID >> 1] = [module, method];
|
|
onFail && params.push(this._callbackID);
|
|
this._callbacks[this._callbackID++] = onFail;
|
|
onSucc && params.push(this._callbackID);
|
|
this._callbacks[this._callbackID++] = onSucc;
|
|
}
|
|
|
|
global.nativeTraceBeginAsyncFlow &&
|
|
global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 'native', this._callID);
|
|
this._callID++;
|
|
|
|
this._queue[MODULE_IDS].push(module);
|
|
this._queue[METHOD_IDS].push(method);
|
|
this._queue[PARAMS].push(params);
|
|
|
|
var now = new Date().getTime();
|
|
if (global.nativeFlushQueueImmediate &&
|
|
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
|
|
global.nativeFlushQueueImmediate(this._queue);
|
|
this._queue = [[], [], [], this._callID];
|
|
this._lastFlush = now;
|
|
}
|
|
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
|
|
if (__DEV__ && SPY_MODE && isFinite(module)) {
|
|
console.log('JS->N : ' + this._remoteModuleTable[module] + '.' +
|
|
this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')');
|
|
}
|
|
}
|
|
|
|
__callFunction(module, method, args) {
|
|
this._lastFlush = new Date().getTime();
|
|
this._eventLoopStartTime = this._lastFlush;
|
|
if (isFinite(module)) {
|
|
method = this._methodTable[module][method];
|
|
module = this._moduleTable[module];
|
|
}
|
|
Systrace.beginEvent(`${module}.${method}()`);
|
|
if (__DEV__ && SPY_MODE) {
|
|
console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')');
|
|
}
|
|
var moduleMethods = this._callableModules[module];
|
|
invariant(
|
|
!!moduleMethods,
|
|
'Module %s is not a registered callable module.',
|
|
module
|
|
);
|
|
moduleMethods[method].apply(moduleMethods, args);
|
|
Systrace.endEvent();
|
|
}
|
|
|
|
__invokeCallback(cbID, args) {
|
|
this._lastFlush = new Date().getTime();
|
|
this._eventLoopStartTime = this._lastFlush;
|
|
let callback = this._callbacks[cbID];
|
|
let debug = this._debugInfo[cbID >> 1];
|
|
let module = debug && this._remoteModuleTable[debug[0]];
|
|
let method = debug && this._remoteMethodTable[debug[0]][debug[1]];
|
|
invariant(
|
|
callback,
|
|
`Callback with id ${cbID}: ${module}.${method}() not found`
|
|
);
|
|
let profileName = debug ? '<callback for ' + module + '.' + method + '>' : cbID;
|
|
if (callback && SPY_MODE && __DEV__) {
|
|
console.log('N->JS : ' + profileName + '(' + JSON.stringify(args) + ')');
|
|
}
|
|
Systrace.beginEvent(
|
|
`MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`);
|
|
this._callbacks[cbID & ~1] = null;
|
|
this._callbacks[cbID | 1] = null;
|
|
callback.apply(null, args);
|
|
Systrace.endEvent();
|
|
}
|
|
|
|
/**
|
|
* Private helper methods
|
|
*/
|
|
|
|
/**
|
|
* Converts the old, object-based module structure to the new
|
|
* array-based structure. TODO (t8823865) Removed this
|
|
* function once Android has been updated.
|
|
*/
|
|
_genModulesConfig(modules /* array or object */) {
|
|
if (Array.isArray(modules)) {
|
|
return modules;
|
|
} else {
|
|
let moduleArray = [];
|
|
let moduleNames = Object.keys(modules);
|
|
for (var i = 0, l = moduleNames.length; i < l; i++) {
|
|
let moduleName = moduleNames[i];
|
|
let moduleConfig = modules[moduleName];
|
|
let module = [moduleName];
|
|
if (moduleConfig.constants) {
|
|
module.push(moduleConfig.constants);
|
|
}
|
|
let methodsConfig = moduleConfig.methods;
|
|
if (methodsConfig) {
|
|
let methods = [];
|
|
let asyncMethods = [];
|
|
let methodNames = Object.keys(methodsConfig);
|
|
for (var j = 0, ll = methodNames.length; j < ll; j++) {
|
|
let methodName = methodNames[j];
|
|
let methodConfig = methodsConfig[methodName];
|
|
methods[methodConfig.methodID] = methodName;
|
|
if (methodConfig.type === MethodTypes.remoteAsync) {
|
|
asyncMethods.push(methodConfig.methodID);
|
|
}
|
|
}
|
|
if (methods.length) {
|
|
module.push(methods);
|
|
if (asyncMethods.length) {
|
|
module.push(asyncMethods);
|
|
}
|
|
}
|
|
}
|
|
moduleArray[moduleConfig.moduleID] = module;
|
|
}
|
|
return moduleArray;
|
|
}
|
|
}
|
|
|
|
_genLookupTables(modulesConfig, moduleTable, methodTable) {
|
|
modulesConfig.forEach((config, moduleID) => {
|
|
this._genLookup(config, moduleID, moduleTable, methodTable);
|
|
});
|
|
}
|
|
|
|
_genLookup(config, moduleID, moduleTable, methodTable) {
|
|
if (!config) {
|
|
return;
|
|
}
|
|
|
|
let moduleName, methods;
|
|
if (moduleHasConstants(config)) {
|
|
[moduleName, , methods] = config;
|
|
} else {
|
|
[moduleName, methods] = config;
|
|
}
|
|
|
|
moduleTable[moduleID] = moduleName;
|
|
methodTable[moduleID] = Object.assign({}, methods);
|
|
}
|
|
|
|
_genModules(remoteModules) {
|
|
remoteModules.forEach((config, moduleID) => {
|
|
this._genModule(config, moduleID);
|
|
});
|
|
}
|
|
|
|
_genModule(config, moduleID) {
|
|
if (!config) {
|
|
return;
|
|
}
|
|
|
|
let moduleName, constants, methods, asyncMethods;
|
|
if (moduleHasConstants(config)) {
|
|
[moduleName, constants, methods, asyncMethods] = config;
|
|
} else {
|
|
[moduleName, methods, asyncMethods] = config;
|
|
}
|
|
|
|
let module = {};
|
|
methods && methods.forEach((methodName, methodID) => {
|
|
const methodType =
|
|
asyncMethods && arrayContains(asyncMethods, methodID) ?
|
|
MethodTypes.remoteAsync : MethodTypes.remote;
|
|
module[methodName] = this._genMethod(moduleID, methodID, methodType);
|
|
});
|
|
Object.assign(module, constants);
|
|
|
|
if (!constants && !methods && !asyncMethods) {
|
|
module.moduleID = moduleID;
|
|
}
|
|
|
|
this.RemoteModules[moduleName] = module;
|
|
return module;
|
|
}
|
|
|
|
_genMethod(module, method, type) {
|
|
let fn = null;
|
|
let self = this;
|
|
if (type === MethodTypes.remoteAsync) {
|
|
fn = function(...args) {
|
|
return new Promise((resolve, reject) => {
|
|
self.__nativeCall(
|
|
module,
|
|
method,
|
|
args,
|
|
(data) => {
|
|
resolve(data);
|
|
},
|
|
(errorData) => {
|
|
var error = createErrorFromErrorData(errorData);
|
|
reject(error);
|
|
});
|
|
});
|
|
};
|
|
} else {
|
|
fn = function(...args) {
|
|
let lastArg = args.length > 0 ? args[args.length - 1] : null;
|
|
let secondLastArg = args.length > 1 ? args[args.length - 2] : null;
|
|
let hasSuccCB = typeof lastArg === 'function';
|
|
let hasErrorCB = typeof secondLastArg === 'function';
|
|
hasErrorCB && invariant(
|
|
hasSuccCB,
|
|
'Cannot have a non-function arg after a function arg.'
|
|
);
|
|
let numCBs = hasSuccCB + hasErrorCB;
|
|
let onSucc = hasSuccCB ? lastArg : null;
|
|
let onFail = hasErrorCB ? secondLastArg : null;
|
|
args = args.slice(0, args.length - numCBs);
|
|
return self.__nativeCall(module, method, args, onFail, onSucc);
|
|
};
|
|
}
|
|
fn.type = type;
|
|
return fn;
|
|
}
|
|
|
|
registerCallableModule(name, methods) {
|
|
this._callableModules[name] = methods;
|
|
}
|
|
|
|
}
|
|
|
|
function moduleHasConstants(moduleArray: Array<Object|Array<>>): boolean {
|
|
return !Array.isArray(moduleArray[1]);
|
|
}
|
|
|
|
function arrayContains<T>(array: Array<T>, value: T): boolean {
|
|
return array.indexOf(value) !== -1;
|
|
}
|
|
|
|
function createErrorFromErrorData(errorData: {message: string}): Error {
|
|
var {
|
|
message,
|
|
...extraErrorInfo,
|
|
} = errorData;
|
|
var error = new Error(message);
|
|
error.framesToPop = 1;
|
|
return Object.assign(error, extraErrorInfo);
|
|
}
|
|
|
|
module.exports = MessageQueue;
|