asyncawait
Version:
async/await for node.js
225 lines • 11.3 kB
JavaScript
var Fiber = require('../fibers');
var _ = require('lodash');
var Config = require('./config');
var FiberMgr = require('./fiberManager');
var RunContext = require('./runContext');
var Semaphore = require('./semaphore');
var AsyncIterator = require('./asyncIterator');
var defer = require('./defer');
var await = require('../await/index');
/** Function for creating a specific variant of the async function. */
function makeAsyncFunc(config) {
// Validate the specified configuration
config.validate();
// Create an async function tailored to the given options.
var result = function async(bodyFunc) {
// Create a semaphore for limiting top-level concurrency, if specified in options.
var semaphore = config.maxConcurrency ? new Semaphore(config.maxConcurrency) : Semaphore.unlimited;
// Choose and run the appropriate function factory based on whether the result should be iterable.
var makeFunc = config.isIterable ? makeAsyncIterator : makeAsyncNonIterator;
var result = makeFunc(bodyFunc, config, semaphore);
// Ensure the suspendable function's arity matches that of the function it wraps.
var arity = bodyFunc.length;
if (config.acceptsCallback)
++arity;
result = makeFuncWithArity(result, arity);
return result;
};
// Add the mod() function, and return the result.
result.mod = makeModFunc(config);
return result;
}
/** Function for creating iterable suspendable functions. */
function makeAsyncIterator(bodyFunc, config, semaphore) {
// Return a function that returns an iterator.
return function iterable() {
// Capture the initial arguments used to start the iterator, as an array.
var startupArgs = new Array(arguments.length + 1); // Reserve 0th arg for the yield function.
for (var i = 0, len = arguments.length; i < len; ++i)
startupArgs[i + 1] = arguments[i];
// Create a yield() function tailored for this iterator.
var yield_ = function (expr) {
// Ensure this function is executing inside a fiber.
if (!Fiber.current) {
throw new Error('await functions, yield functions, and value-returning suspendable ' +
'functions may only be called from inside a suspendable function. ');
}
// Notify waiters of the next result, then suspend the iterator.
if (runContext.callback)
runContext.callback(null, { value: expr, done: false });
if (runContext.resolver)
runContext.resolver.resolve({ value: expr, done: false });
Fiber.yield();
};
// Insert the yield function as the first argument when starting the iterator.
startupArgs[0] = yield_;
// Create the iterator.
var runContext = new RunContext(bodyFunc, this, startupArgs);
var iterator = new AsyncIterator(runContext, semaphore, config.returnValue, config.acceptsCallback);
// Wrap the given bodyFunc to properly complete the iteration.
runContext.wrapped = function () {
var len = arguments.length, args = new Array(len);
for (var i = 0; i < len; ++i)
args[i] = arguments[i];
bodyFunc.apply(this, args);
iterator.destroy();
return { done: true };
};
// Return the iterator.
return iterator;
};
}
/** Function for creating non-iterable suspendable functions. */
function makeAsyncNonIterator(bodyFunc, config, semaphore) {
// Return a function that executes fn in a fiber and returns a promise of fn's result.
return function nonIterable() {
// Get all the arguments passed in, as an array.
var argsAsArray = new Array(arguments.length);
for (var i = 0; i < argsAsArray.length; ++i)
argsAsArray[i] = arguments[i];
// Remove concurrency restrictions for nested calls, to avoid race conditions.
if (FiberMgr.isExecutingInFiber())
this._semaphore = Semaphore.unlimited;
// Configure the run context.
var runContext = new RunContext(bodyFunc, this, argsAsArray, function () { return semaphore.leave(); });
if (config.returnValue !== Config.NONE) {
var resolver = defer();
runContext.resolver = resolver;
}
if (config.acceptsCallback && argsAsArray.length && _.isFunction(argsAsArray[argsAsArray.length - 1])) {
var callback = argsAsArray.pop();
runContext.callback = callback;
}
// Execute bodyFunc to completion in a coroutine. For thunks, this is a lazy operation.
if (config.returnValue === Config.THUNK) {
var thunk = function (done) {
if (done)
resolver.promise.then(function (val) { return done(null, val); }, function (err) { return done(err); });
semaphore.enter(function () { return FiberMgr.create().run(runContext); });
};
}
else {
semaphore.enter(function () { return FiberMgr.create().run(runContext); });
}
// Return the appropriate value.
switch (config.returnValue) {
case Config.PROMISE: return resolver.promise;
case Config.THUNK: return thunk;
case Config.RESULT: return await(resolver.promise);
case Config.NONE: return;
}
};
}
/** Returns a function that directly proxies the given function, whilst reporting the given arity. */
function makeFuncWithArity(fn, arity) {
// Need to handle each arity individually, but the body never changes.
switch (arity) {
case 0: return function f0() { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
r[i] = arguments[i]; return fn.apply(this, r); };
case 1: return function f1(a) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
r[i] = arguments[i]; return fn.apply(this, r); };
case 2: return function f2(a, b) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
r[i] = arguments[i]; return fn.apply(this, r); };
case 3: return function f3(a, b, c) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
r[i] = arguments[i]; return fn.apply(this, r); };
case 4: return function f4(a, b, c, d) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
r[i] = arguments[i]; return fn.apply(this, r); };
case 5: return function f5(a, b, c, d, e) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
r[i] = arguments[i]; return fn.apply(this, r); };
case 6: return function f6(a, b, c, d, e, f) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
r[i] = arguments[i]; return fn.apply(this, r); };
case 7: return function f7(a, b, c, d, e, f, g) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
r[i] = arguments[i]; return fn.apply(this, r); };
case 8: return function f8(a, b, c, d, e, f, g, h) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
r[i] = arguments[i]; return fn.apply(this, r); };
case 9: return function f9(a, b, c, d, e, f, g, h, _i) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
r[i] = arguments[i]; return fn.apply(this, r); };
default: return fn; // Bail out if arity is crazy high.
}
}
function makeModFunc(config) {
return function (options, maxConcurrency) {
if (_.isString(options)) {
// This way of specifying options is useful for TypeScript users, as they get better type information.
// JavaScript users can use this too, but providing an options hash is more useful in that case.
var rt, cb, it;
switch (options) {
case 'returns: promise, callback: false, iterable: false':
rt = 'promise';
cb = false;
it = false;
break;
case 'returns: thunk, callback: false, iterable: false':
rt = 'thunk';
cb = false;
it = false;
break;
case 'returns: result, callback: false, iterable: false':
rt = 'result';
cb = false;
it = false;
break;
case 'returns: promise, callback: true, iterable: false':
rt = 'promise';
cb = true;
it = false;
break;
case 'returns: thunk, callback: true, iterable: false':
rt = 'thunk';
cb = true;
it = false;
break;
case 'returns: result, callback: true, iterable: false':
rt = 'result';
cb = true;
it = false;
break;
case 'returns: none, callback: true, iterable: false':
rt = 'none';
cb = true;
it = false;
break;
case 'returns: promise, callback: false, iterable: true':
rt = 'promise';
cb = false;
it = true;
break;
case 'returns: thunk, callback: false, iterable: true':
rt = 'thunk';
cb = false;
it = true;
break;
case 'returns: result, callback: false, iterable: true':
rt = 'result';
cb = false;
it = true;
break;
case 'returns: promise, callback: true, iterable: true':
rt = 'promise';
cb = true;
it = true;
break;
case 'returns: thunk, callback: true, iterable: true':
rt = 'thunk';
cb = true;
it = true;
break;
case 'returns: result, callback: true, iterable: true':
rt = 'result';
cb = true;
it = true;
break;
case 'returns: none, callback: true, iterable: true':
rt = 'none';
cb = true;
it = true;
break;
}
options = { returnValue: rt, acceptsCallback: cb, isIterable: it, maxConcurrency: maxConcurrency };
}
var newConfig = new Config(_.defaults({}, options, config));
return makeAsyncFunc(newConfig);
};
}
module.exports = makeAsyncFunc;
//# sourceMappingURL=makeAsyncFunc.js.map