blob: 6b7e8d408c5dcb81ae39ed29b796f751c0f644ee [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file defines a async calling convention intended used to call
// user-defined functions.
var verror = require('../gen-vdl/v.io/v23/verror');
var logger = require('../lib/vlog').logger;
module.exports = asyncCall;
/**
* asyncCall performs a call and calls a callback with the result.
*
* The called function must either return a promise or call a callback and
* return undefined.
*
* @private
* @param {Context} ctx Context
* @param {*} self The object to be "this" during invocation.
* @param {InspectableFunction} fn The function
* @param {number} numOutArgs The number of expected output arguments
* @param {*} args The argument values
* @param {Function} inputCb callback when finished
* @return {type} Promise or undefined
*/
function asyncCall(ctx, self, fn, numOutArgs, args, inputCb) {
var cbCalled;
// Helper to call the callback once
function callOnceCb(err, results) {
if (cbCalled) {
logger.error('Callback called multiple times');
return;
}
inputCb.apply(this, arguments);
cbCalled = true;
}
// Call the callback and log the error. Used for internal errors.
function asyncFailedCb(err) {
logger.error(err);
callOnceCb(err);
}
// Callback we are injecting into the user's function
function injectedCb(err /*, args */) {
var res = Array.prototype.slice.call(arguments, 1,
1 + numOutArgs);
var paddingNeeded = numOutArgs - res.length;
var paddedRes = res.concat(new Array(paddingNeeded));
callOnceCb(err, paddedRes);
}
if (fn.hasCallback()) {
args.push(injectedCb);
}
var result;
try {
result = fn.apply(self, args);
} catch (err) {
logger.error('Caught error: ', err);
callOnceCb(wrapError(err));
return;
}
// Callback case (wait for callback to be called directly):
if (fn.hasCallback()) {
return;
}
// Promise / direct return case:
Promise.resolve(result).then(function(res) {
// We expect:
// 0 args - return; // NOT return [];
// 1 args - return a; // NOT return [a];
// 2 args - return [a, b] ;
//
// Convert the results to always be in array style:
// [], [a], [a, b], etc
var resAsArray;
switch (numOutArgs) {
case 0:
if (res !== undefined) {
return asyncFailedCb(new verror.InternalError(ctx,
'Non-undefined value returned from function with 0 out args'));
}
resAsArray = [];
break;
case 1:
resAsArray = [res];
break;
default:
if (!Array.isArray(res)) {
asyncFailedCb(new verror.InternalError(
ctx,
'Expected multiple out arguments to be returned in an array.'));
}
resAsArray = res;
break;
}
if (resAsArray.length !== numOutArgs) {
// -1 on outArgs.length ignores error
// TODO(bjornick): Generate a real verror for this so it can
// internationalized.
asyncFailedCb(new verror.InternalError(
ctx,
'Expected', numOutArgs, 'results, but got',
resAsArray.length));
}
callOnceCb(null, resAsArray);
}).catch(function error(err) {
callOnceCb(wrapError(err));
});
}
/**
* Wrap an error so that it is always of type Error.
* This is used in cases where values are known to be errors even if they
* are not of error type such as if they are thrown or rejected.
* @private
* @param {Error} err The error or other value.
* @return {Error} An error or type Error.
*/
function wrapError(err) {
if (!(err instanceof Error)) {
return new Error(err);
} else {
return err;
}
}