alpaca
Version:
Alpaca provides the easiest and fastest way to generate interactive forms for the web and mobile devices. It runs simply as HTML5 or more elaborately using Bootstrap, jQuery Mobile or jQuery UI. Alpaca uses Handlebars to process JSON schema and provide
1,423 lines (1,282 loc) • 180 kB
JavaScript
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
//
// ASYNC
//
// Here we provide a reduced version of the wonderful async library. This is entirely inline and
// will have no bearing on any external dependencies on async.
//
// https://github.com/caolan/async
// Copyright (c) 2010 Caolan McMahon
//
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
(function() {
//'use strict';
var async = {};
Alpaca.series = Alpaca.serial = function(funcs, callback)
{
async.series(funcs, function() {
callback();
});
};
Alpaca.parallel = function(funcs, callback)
{
async.parallel(funcs, function() {
callback();
});
};
Alpaca.nextTick = function(f)
{
async.nextTick(function() {
f();
});
};
// here is where async begins
/**
* Creates a continuation function with some arguments already applied.
*
* Useful as a shorthand when combined with other control flow functions. Any
* arguments passed to the returned function are added to the arguments
* originally passed to apply.
*
* @name apply
* @static
* @memberOf module:Utils
* @method
* @category Util
* @param {Function} fn - The function you want to eventually apply all
* arguments to. Invokes with (arguments...).
* @param {...*} arguments... - Any number of arguments to automatically apply
* when the continuation is called.
* @returns {Function} the partially-applied function
* @example
*
* // using apply
* async.parallel([
* async.apply(fs.writeFile, 'testfile1', 'test1'),
* async.apply(fs.writeFile, 'testfile2', 'test2')
* ]);
*
*
* // the same process without using apply
* async.parallel([
* function(callback) {
* fs.writeFile('testfile1', 'test1', callback);
* },
* function(callback) {
* fs.writeFile('testfile2', 'test2', callback);
* }
* ]);
*
* // It's possible to pass any number of additional arguments when calling the
* // continuation:
*
* node> var fn = async.apply(sys.puts, 'one');
* node> fn('two', 'three');
* one
* two
* three
*/
function apply(fn, ...args) {
return (...callArgs) => fn(...args,...callArgs);
}
function initialParams (fn) {
return function (...args/*, callback*/) {
var callback = args.pop();
return fn.call(this, args, callback);
};
}
var hasSetImmediate = typeof setImmediate === 'function' && setImmediate;
var hasNextTick = typeof process === 'object' && typeof process.nextTick === 'function';
function fallback(fn) {
setTimeout(fn, 0);
}
function wrap(defer) {
return (fn, ...args) => defer(() => fn(...args));
}
var _defer;
if (hasSetImmediate) {
_defer = setImmediate;
} else if (hasNextTick) {
_defer = process.nextTick;
} else {
_defer = fallback;
}
var setImmediate$1 = wrap(_defer);
/**
* Take a sync function and make it async, passing its return value to a
* callback. This is useful for plugging sync functions into a waterfall,
* series, or other async functions. Any arguments passed to the generated
* function will be passed to the wrapped function (except for the final
* callback argument). Errors thrown will be passed to the callback.
*
* If the function passed to `asyncify` returns a Promise, that promises's
* resolved/rejected state will be used to call the callback, rather than simply
* the synchronous return value.
*
* This also means you can asyncify ES2017 `async` functions.
*
* @name asyncify
* @static
* @memberOf module:Utils
* @method
* @alias wrapSync
* @category Util
* @param {Function} func - The synchronous function, or Promise-returning
* function to convert to an {@link AsyncFunction}.
* @returns {AsyncFunction} An asynchronous wrapper of the `func`. To be
* invoked with `(args..., callback)`.
* @example
*
* // passing a regular synchronous function
* async.waterfall([
* async.apply(fs.readFile, filename, "utf8"),
* async.asyncify(JSON.parse),
* function (data, next) {
* // data is the result of parsing the text.
* // If there was a parsing error, it would have been caught.
* }
* ], callback);
*
* // passing a function returning a promise
* async.waterfall([
* async.apply(fs.readFile, filename, "utf8"),
* async.asyncify(function (contents) {
* return db.model.create(contents);
* }),
* function (model, next) {
* // `model` is the instantiated model object.
* // If there was an error, this function would be skipped.
* }
* ], callback);
*
* // es2017 example, though `asyncify` is not needed if your JS environment
* // supports async functions out of the box
* var q = async.queue(async.asyncify(async function(file) {
* var intermediateStep = await processFile(file);
* return await somePromise(intermediateStep)
* }));
*
* q.push(files);
*/
function asyncify(func) {
if (isAsync(func)) {
return function (...args/*, callback*/) {
const callback = args.pop();
const promise = func.apply(this, args);
return handlePromise(promise, callback)
}
}
return initialParams(function (args, callback) {
var result;
try {
result = func.apply(this, args);
} catch (e) {
return callback(e);
}
// if result is Promise object
if (result && typeof result.then === 'function') {
return handlePromise(result, callback)
} else {
callback(null, result);
}
});
}
function handlePromise(promise, callback) {
return promise.then(value => {
invokeCallback(callback, null, value);
}, err => {
invokeCallback(callback, err && err.message ? err : new Error(err));
});
}
function invokeCallback(callback, error, value) {
try {
callback(error, value);
} catch (err) {
setImmediate$1(e => { throw e }, err);
}
}
function isAsync(fn) {
return fn[Symbol.toStringTag] === 'AsyncFunction';
}
function isAsyncGenerator(fn) {
return fn[Symbol.toStringTag] === 'AsyncGenerator';
}
function isAsyncIterable(obj) {
return typeof obj[Symbol.asyncIterator] === 'function';
}
function wrapAsync(asyncFn) {
if (typeof asyncFn !== 'function') throw new Error('expected a function')
return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn;
}
function applyEach(eachfn) {
return function(fns, ...callArgs) {
var go = initialParams(function(args, callback) {
var that = this;
return eachfn(fns, (fn, cb) => {
wrapAsync(fn).apply(that, args.concat(cb));
}, callback);
});
if (callArgs.length) {
return go.apply(this, callArgs);
}
return go;
};
}
function _asyncMap(eachfn, arr, iteratee, callback) {
arr = arr || [];
var results = [];
var counter = 0;
var _iteratee = wrapAsync(iteratee);
return eachfn(arr, (value, _, iterCb) => {
var index = counter++;
_iteratee(value, (err, v) => {
results[index] = v;
iterCb(err);
});
}, err => {
callback(err, results);
});
}
function isArrayLike(value) {
return value &&
typeof value.length === 'number' &&
value.length >= 0 &&
value.length % 1 === 0;
}
// A temporary value used to identify if the loop should be broken.
// See #1064, #1293
const breakLoop = {};
function once(fn) {
function wrapper (...args) {
if (fn === null) return;
var callFn = fn;
fn = null;
callFn.apply(this, args);
}
Object.assign(wrapper, fn);
return wrapper
}
function getIterator (coll) {
return coll[Symbol.iterator] && coll[Symbol.iterator]();
}
function createArrayIterator(coll) {
var i = -1;
var len = coll.length;
return function next() {
return ++i < len ? {value: coll[i], key: i} : null;
}
}
function createES2015Iterator(iterator) {
var i = -1;
return function next() {
var item = iterator.next();
if (item.done)
return null;
i++;
return {value: item.value, key: i};
}
}
function createObjectIterator(obj) {
var okeys = obj ? Object.keys(obj) : [];
var i = -1;
var len = okeys.length;
return function next() {
var key = okeys[++i];
return i < len ? {value: obj[key], key} : null;
};
}
function createIterator(coll) {
if (isArrayLike(coll)) {
return createArrayIterator(coll);
}
var iterator = getIterator(coll);
return iterator ? createES2015Iterator(iterator) : createObjectIterator(coll);
}
function onlyOnce(fn) {
return function (...args) {
if (fn === null) throw new Error("Callback was already called.");
var callFn = fn;
fn = null;
callFn.apply(this, args);
};
}
// for async generators
function asyncEachOfLimit(generator, limit, iteratee, callback) {
let done = false;
let canceled = false;
let awaiting = false;
let running = 0;
let idx = 0;
function replenish() {
//console.log('replenish')
if (running >= limit || awaiting || done) return
//console.log('replenish awaiting')
awaiting = true;
generator.next().then(({value, done: iterDone}) => {
//console.log('got value', value)
if (canceled || done) return
awaiting = false;
if (iterDone) {
done = true;
if (running <= 0) {
//console.log('done nextCb')
callback(null);
}
return;
}
running++;
iteratee(value, idx, iterateeCallback);
idx++;
replenish();
}).catch(handleError);
}
function iterateeCallback(err, result) {
//console.log('iterateeCallback')
running -= 1;
if (canceled) return
if (err) return handleError(err)
if (err === false) {
done = true;
canceled = true;
return
}
if (result === breakLoop || (done && running <= 0)) {
done = true;
//console.log('done iterCb')
return callback(null);
}
replenish();
}
function handleError(err) {
if (canceled) return
awaiting = false;
done = true;
callback(err);
}
replenish();
}
var eachOfLimit = (limit) => {
return (obj, iteratee, callback) => {
callback = once(callback);
if (limit <= 0) {
throw new RangeError('concurrency limit cannot be less than 1')
}
if (!obj) {
return callback(null);
}
if (isAsyncGenerator(obj)) {
return asyncEachOfLimit(obj, limit, iteratee, callback)
}
if (isAsyncIterable(obj)) {
return asyncEachOfLimit(obj[Symbol.asyncIterator](), limit, iteratee, callback)
}
var nextElem = createIterator(obj);
var done = false;
var canceled = false;
var running = 0;
var looping = false;
function iterateeCallback(err, value) {
if (canceled) return
running -= 1;
if (err) {
done = true;
callback(err);
}
else if (err === false) {
done = true;
canceled = true;
}
else if (value === breakLoop || (done && running <= 0)) {
done = true;
return callback(null);
}
else if (!looping) {
replenish();
}
}
function replenish () {
looping = true;
while (running < limit && !done) {
var elem = nextElem();
if (elem === null) {
done = true;
if (running <= 0) {
callback(null);
}
return;
}
running += 1;
iteratee(elem.value, elem.key, onlyOnce(iterateeCallback));
}
looping = false;
}
replenish();
};
};
// conditionally promisify a function.
// only return a promise if a callback is omitted
function awaitify (asyncFn, arity = asyncFn.length) {
if (!arity) throw new Error('arity is undefined')
function awaitable (...args) {
if (typeof args[arity - 1] === 'function') {
return asyncFn.apply(this, args)
}
return new Promise((resolve, reject) => {
args[arity - 1] = (err, ...cbArgs) => {
if (err) return reject(err)
resolve(cbArgs.length > 1 ? cbArgs : cbArgs[0]);
};
asyncFn.apply(this, args);
})
}
Object.defineProperty(awaitable, 'name', {
value: `awaitable(${asyncFn.name})`
});
return awaitable
}
/**
* The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a
* time.
*
* @name eachOfLimit
* @static
* @memberOf module:Collections
* @method
* @see [async.eachOf]{@link module:Collections.eachOf}
* @alias forEachOfLimit
* @category Collection
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
* @param {number} limit - The maximum number of async operations at a time.
* @param {AsyncFunction} iteratee - An async function to apply to each
* item in `coll`. The `key` is the item's key, or index in the case of an
* array.
* Invoked with (item, key, callback).
* @param {Function} [callback] - A callback which is called when all
* `iteratee` functions have finished, or an error occurs. Invoked with (err).
* @returns {Promise} a promise, if a callback is omitted
*/
function eachOfLimit$1(coll, limit, iteratee, callback) {
return eachOfLimit(limit)(coll, wrapAsync(iteratee), callback);
}
var eachOfLimit$2 = awaitify(eachOfLimit$1, 4);
// eachOf implementation optimized for array-likes
function eachOfArrayLike(coll, iteratee, callback) {
callback = once(callback);
var index = 0,
completed = 0,
{length} = coll,
canceled = false;
if (length === 0) {
callback(null);
}
function iteratorCallback(err, value) {
if (err === false) {
canceled = true;
}
if (canceled === true) return
if (err) {
callback(err);
} else if ((++completed === length) || value === breakLoop) {
callback(null);
}
}
for (; index < length; index++) {
iteratee(coll[index], index, onlyOnce(iteratorCallback));
}
}
// a generic version of eachOf which can handle array, object, and iterator cases.
function eachOfGeneric (coll, iteratee, callback) {
return eachOfLimit$2(coll, Infinity, iteratee, callback);
}
/**
* Like [`each`]{@link module:Collections.each}, except that it passes the key (or index) as the second argument
* to the iteratee.
*
* @name eachOf
* @static
* @memberOf module:Collections
* @method
* @alias forEachOf
* @category Collection
* @see [async.each]{@link module:Collections.each}
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
* @param {AsyncFunction} iteratee - A function to apply to each
* item in `coll`.
* The `key` is the item's key, or index in the case of an array.
* Invoked with (item, key, callback).
* @param {Function} [callback] - A callback which is called when all
* `iteratee` functions have finished, or an error occurs. Invoked with (err).
* @returns {Promise} a promise, if a callback is omitted
* @example
*
* var obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"};
* var configs = {};
*
* async.forEachOf(obj, function (value, key, callback) {
* fs.readFile(__dirname + value, "utf8", function (err, data) {
* if (err) return callback(err);
* try {
* configs[key] = JSON.parse(data);
* } catch (e) {
* return callback(e);
* }
* callback();
* });
* }, function (err) {
* if (err) console.error(err.message);
* // configs is now a map of JSON data
* doSomethingWith(configs);
* });
*/
function eachOf(coll, iteratee, callback) {
var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric;
return eachOfImplementation(coll, wrapAsync(iteratee), callback);
}
var eachOf$1 = awaitify(eachOf, 3);
/**
* Produces a new collection of values by mapping each value in `coll` through
* the `iteratee` function. The `iteratee` is called with an item from `coll`
* and a callback for when it has finished processing. Each of these callback
* takes 2 arguments: an `error`, and the transformed item from `coll`. If
* `iteratee` passes an error to its callback, the main `callback` (for the
* `map` function) is immediately called with the error.
*
* Note, that since this function applies the `iteratee` to each item in
* parallel, there is no guarantee that the `iteratee` functions will complete
* in order. However, the results array will be in the same order as the
* original `coll`.
*
* If `map` is passed an Object, the results will be an Array. The results
* will roughly be in the order of the original Objects' keys (but this can
* vary across JavaScript engines).
*
* @name map
* @static
* @memberOf module:Collections
* @method
* @category Collection
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
* @param {AsyncFunction} iteratee - An async function to apply to each item in
* `coll`.
* The iteratee should complete with the transformed item.
* Invoked with (item, callback).
* @param {Function} [callback] - A callback which is called when all `iteratee`
* functions have finished, or an error occurs. Results is an Array of the
* transformed items from the `coll`. Invoked with (err, results).
* @returns {Promise} a promise, if no callback is passed
* @example
*
* async.map(['file1','file2','file3'], fs.stat, function(err, results) {
* // results is now an array of stats for each file
* });
*/
function map (coll, iteratee, callback) {
return _asyncMap(eachOf$1, coll, iteratee, callback)
}
var map$1 = awaitify(map, 3);
/**
* Applies the provided arguments to each function in the array, calling
* `callback` after all functions have completed. If you only provide the first
* argument, `fns`, then it will return a function which lets you pass in the
* arguments as if it were a single function call. If more arguments are
* provided, `callback` is required while `args` is still optional. The results
* for each of the applied async functions are passed to the final callback
* as an array.
*
* @name applyEach
* @static
* @memberOf module:ControlFlow
* @method
* @category Control Flow
* @param {Array|Iterable|AsyncIterable|Object} fns - A collection of {@link AsyncFunction}s
* to all call with the same arguments
* @param {...*} [args] - any number of separate arguments to pass to the
* function.
* @param {Function} [callback] - the final argument should be the callback,
* called when all functions have completed processing.
* @returns {Function} - If only the first argument, `fns`, is provided, it will
* return a function which lets you pass in the arguments as if it were a single
* function call. The signature is `(..args, callback)`. If invoked with any
* arguments, `callback` is required.
* @example
*
* async.applyEach([enableSearch, updateSchema], 'bucket', (err, results) => {
* // results[0] is the results for `enableSearch`
* // results[1] is the results for `updateSchema`
* });
*
* // partial application example:
* async.each(
* buckets,
* async.applyEach([enableSearch, updateSchema]),
* callback
* );
*/
var applyEach$1 = applyEach(map$1);
/**
* The same as [`eachOf`]{@link module:Collections.eachOf} but runs only a single async operation at a time.
*
* @name eachOfSeries
* @static
* @memberOf module:Collections
* @method
* @see [async.eachOf]{@link module:Collections.eachOf}
* @alias forEachOfSeries
* @category Collection
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
* @param {AsyncFunction} iteratee - An async function to apply to each item in
* `coll`.
* Invoked with (item, key, callback).
* @param {Function} [callback] - A callback which is called when all `iteratee`
* functions have finished, or an error occurs. Invoked with (err).
* @returns {Promise} a promise, if a callback is omitted
*/
function eachOfSeries(coll, iteratee, callback) {
return eachOfLimit$2(coll, 1, iteratee, callback)
}
var eachOfSeries$1 = awaitify(eachOfSeries, 3);
/**
* The same as [`map`]{@link module:Collections.map} but runs only a single async operation at a time.
*
* @name mapSeries
* @static
* @memberOf module:Collections
* @method
* @see [async.map]{@link module:Collections.map}
* @category Collection
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
* @param {AsyncFunction} iteratee - An async function to apply to each item in
* `coll`.
* The iteratee should complete with the transformed item.
* Invoked with (item, callback).
* @param {Function} [callback] - A callback which is called when all `iteratee`
* functions have finished, or an error occurs. Results is an array of the
* transformed items from the `coll`. Invoked with (err, results).
* @returns {Promise} a promise, if no callback is passed
*/
function mapSeries (coll, iteratee, callback) {
return _asyncMap(eachOfSeries$1, coll, iteratee, callback)
}
var mapSeries$1 = awaitify(mapSeries, 3);
/**
* The same as [`applyEach`]{@link module:ControlFlow.applyEach} but runs only a single async operation at a time.
*
* @name applyEachSeries
* @static
* @memberOf module:ControlFlow
* @method
* @see [async.applyEach]{@link module:ControlFlow.applyEach}
* @category Control Flow
* @param {Array|Iterable|AsyncIterable|Object} fns - A collection of {@link AsyncFunction}s to all
* call with the same arguments
* @param {...*} [args] - any number of separate arguments to pass to the
* function.
* @param {Function} [callback] - the final argument should be the callback,
* called when all functions have completed processing.
* @returns {Function} - If only the first argument is provided, it will return
* a function which lets you pass in the arguments as if it were a single
* function call.
*/
var applyEachSeries = applyEach(mapSeries$1);
const PROMISE_SYMBOL = Symbol('promiseCallback');
function promiseCallback () {
let resolve, reject;
function callback (err, ...args) {
if (err) return reject(err)
resolve(args.length > 1 ? args : args[0]);
}
callback[PROMISE_SYMBOL] = new Promise((res, rej) => {
resolve = res,
reject = rej;
});
return callback
}
/**
* Determines the best order for running the {@link AsyncFunction}s in `tasks`, based on
* their requirements. Each function can optionally depend on other functions
* being completed first, and each function is run as soon as its requirements
* are satisfied.
*
* If any of the {@link AsyncFunction}s pass an error to their callback, the `auto` sequence
* will stop. Further tasks will not execute (so any other functions depending
* on it will not run), and the main `callback` is immediately called with the
* error.
*
* {@link AsyncFunction}s also receive an object containing the results of functions which
* have completed so far as the first argument, if they have dependencies. If a
* task function has no dependencies, it will only be passed a callback.
*
* @name auto
* @static
* @memberOf module:ControlFlow
* @method
* @category Control Flow
* @param {Object} tasks - An object. Each of its properties is either a
* function or an array of requirements, with the {@link AsyncFunction} itself the last item
* in the array. The object's key of a property serves as the name of the task
* defined by that property, i.e. can be used when specifying requirements for
* other tasks. The function receives one or two arguments:
* * a `results` object, containing the results of the previously executed
* functions, only passed if the task has any dependencies,
* * a `callback(err, result)` function, which must be called when finished,
* passing an `error` (which can be `null`) and the result of the function's
* execution.
* @param {number} [concurrency=Infinity] - An optional `integer` for
* determining the maximum number of tasks that can be run in parallel. By
* default, as many as possible.
* @param {Function} [callback] - An optional callback which is called when all
* the tasks have been completed. It receives the `err` argument if any `tasks`
* pass an error to their callback. Results are always returned; however, if an
* error occurs, no further `tasks` will be performed, and the results object
* will only contain partial results. Invoked with (err, results).
* @returns {Promise} a promise, if a callback is not passed
* @example
*
* async.auto({
* // this function will just be passed a callback
* readData: async.apply(fs.readFile, 'data.txt', 'utf-8'),
* showData: ['readData', function(results, cb) {
* // results.readData is the file's contents
* // ...
* }]
* }, callback);
*
* async.auto({
* get_data: function(callback) {
* console.log('in get_data');
* // async code to get some data
* callback(null, 'data', 'converted to array');
* },
* make_folder: function(callback) {
* console.log('in make_folder');
* // async code to create a directory to store a file in
* // this is run at the same time as getting the data
* callback(null, 'folder');
* },
* write_file: ['get_data', 'make_folder', function(results, callback) {
* console.log('in write_file', JSON.stringify(results));
* // once there is some data and the directory exists,
* // write the data to a file in the directory
* callback(null, 'filename');
* }],
* email_link: ['write_file', function(results, callback) {
* console.log('in email_link', JSON.stringify(results));
* // once the file is written let's email a link to it...
* // results.write_file contains the filename returned by write_file.
* callback(null, {'file':results.write_file, 'email':'user@example.com'});
* }]
* }, function(err, results) {
* console.log('err = ', err);
* console.log('results = ', results);
* });
*/
function auto(tasks, concurrency, callback) {
if (typeof concurrency !== 'number') {
// concurrency is optional, shift the args.
callback = concurrency;
concurrency = null;
}
callback = once(callback || promiseCallback());
var numTasks = Object.keys(tasks).length;
if (!numTasks) {
return callback(null);
}
if (!concurrency) {
concurrency = numTasks;
}
var results = {};
var runningTasks = 0;
var canceled = false;
var hasError = false;
var listeners = Object.create(null);
var readyTasks = [];
// for cycle detection:
var readyToCheck = []; // tasks that have been identified as reachable
// without the possibility of returning to an ancestor task
var uncheckedDependencies = {};
Object.keys(tasks).forEach(key => {
var task = tasks[key];
if (!Array.isArray(task)) {
// no dependencies
enqueueTask(key, [task]);
readyToCheck.push(key);
return;
}
var dependencies = task.slice(0, task.length - 1);
var remainingDependencies = dependencies.length;
if (remainingDependencies === 0) {
enqueueTask(key, task);
readyToCheck.push(key);
return;
}
uncheckedDependencies[key] = remainingDependencies;
dependencies.forEach(dependencyName => {
if (!tasks[dependencyName]) {
throw new Error('async.auto task `' + key +
'` has a non-existent dependency `' +
dependencyName + '` in ' +
dependencies.join(', '));
}
addListener(dependencyName, () => {
remainingDependencies--;
if (remainingDependencies === 0) {
enqueueTask(key, task);
}
});
});
});
checkForDeadlocks();
processQueue();
function enqueueTask(key, task) {
readyTasks.push(() => runTask(key, task));
}
function processQueue() {
if (canceled) return
if (readyTasks.length === 0 && runningTasks === 0) {
return callback(null, results);
}
while(readyTasks.length && runningTasks < concurrency) {
var run = readyTasks.shift();
run();
}
}
function addListener(taskName, fn) {
var taskListeners = listeners[taskName];
if (!taskListeners) {
taskListeners = listeners[taskName] = [];
}
taskListeners.push(fn);
}
function taskComplete(taskName) {
var taskListeners = listeners[taskName] || [];
taskListeners.forEach(fn => fn());
processQueue();
}
function runTask(key, task) {
if (hasError) return;
var taskCallback = onlyOnce((err, ...result) => {
runningTasks--;
if (err === false) {
canceled = true;
return
}
if (result.length < 2) {
[result] = result;
}
if (err) {
var safeResults = {};
Object.keys(results).forEach(rkey => {
safeResults[rkey] = results[rkey];
});
safeResults[key] = result;
hasError = true;
listeners = Object.create(null);
if (canceled) return
callback(err, safeResults);
} else {
results[key] = result;
taskComplete(key);
}
});
runningTasks++;
var taskFn = wrapAsync(task[task.length - 1]);
if (task.length > 1) {
taskFn(results, taskCallback);
} else {
taskFn(taskCallback);
}
}
function checkForDeadlocks() {
// Kahn's algorithm
// https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
// http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html
var currentTask;
var counter = 0;
while (readyToCheck.length) {
currentTask = readyToCheck.pop();
counter++;
getDependents(currentTask).forEach(dependent => {
if (--uncheckedDependencies[dependent] === 0) {
readyToCheck.push(dependent);
}
});
}
if (counter !== numTasks) {
throw new Error(
'async.auto cannot execute tasks due to a recursive dependency'
);
}
}
function getDependents(taskName) {
var result = [];
Object.keys(tasks).forEach(key => {
const task = tasks[key];
if (Array.isArray(task) && task.indexOf(taskName) >= 0) {
result.push(key);
}
});
return result;
}
return callback[PROMISE_SYMBOL]
}
var FN_ARGS = /^(?:async\s+)?(?:function)?\s*[^(]*\(\s*([^)]+)\s*\)(?:\s*{)/m;
var ARROW_FN_ARGS = /^(?:async\s+)?\(?\s*([^)^=]+)\s*\)?(?:\s*=>)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /(=.+)?(\s*)$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function parseParams(func) {
const src = func.toString().replace(STRIP_COMMENTS, '');
let match = src.match(FN_ARGS);
if (!match) {
match = src.match(ARROW_FN_ARGS);
}
if (!match) throw new Error('could not parse args in autoInject\nSource:\n' + src)
let [, args] = match;
return args
.replace(/\s/g, '')
.split(FN_ARG_SPLIT)
.map((arg) => arg.replace(FN_ARG, '').trim());
}
/**
* A dependency-injected version of the [async.auto]{@link module:ControlFlow.auto} function. Dependent
* tasks are specified as parameters to the function, after the usual callback
* parameter, with the parameter names matching the names of the tasks it
* depends on. This can provide even more readable task graphs which can be
* easier to maintain.
*
* If a final callback is specified, the task results are similarly injected,
* specified as named parameters after the initial error parameter.
*
* The autoInject function is purely syntactic sugar and its semantics are
* otherwise equivalent to [async.auto]{@link module:ControlFlow.auto}.
*
* @name autoInject
* @static
* @memberOf module:ControlFlow
* @method
* @see [async.auto]{@link module:ControlFlow.auto}
* @category Control Flow
* @param {Object} tasks - An object, each of whose properties is an {@link AsyncFunction} of
* the form 'func([dependencies...], callback). The object's key of a property
* serves as the name of the task defined by that property, i.e. can be used
* when specifying requirements for other tasks.
* * The `callback` parameter is a `callback(err, result)` which must be called
* when finished, passing an `error` (which can be `null`) and the result of
* the function's execution. The remaining parameters name other tasks on
* which the task is dependent, and the results from those tasks are the
* arguments of those parameters.
* @param {Function} [callback] - An optional callback which is called when all
* the tasks have been completed. It receives the `err` argument if any `tasks`
* pass an error to their callback, and a `results` object with any completed
* task results, similar to `auto`.
* @returns {Promise} a promise, if no callback is passed
* @example
*
* // The example from `auto` can be rewritten as follows:
* async.autoInject({
* get_data: function(callback) {
* // async code to get some data
* callback(null, 'data', 'converted to array');
* },
* make_folder: function(callback) {
* // async code to create a directory to store a file in
* // this is run at the same time as getting the data
* callback(null, 'folder');
* },
* write_file: function(get_data, make_folder, callback) {
* // once there is some data and the directory exists,
* // write the data to a file in the directory
* callback(null, 'filename');
* },
* email_link: function(write_file, callback) {
* // once the file is written let's email a link to it...
* // write_file contains the filename returned by write_file.
* callback(null, {'file':write_file, 'email':'user@example.com'});
* }
* }, function(err, results) {
* console.log('err = ', err);
* console.log('email_link = ', results.email_link);
* });
*
* // If you are using a JS minifier that mangles parameter names, `autoInject`
* // will not work with plain functions, since the parameter names will be
* // collapsed to a single letter identifier. To work around this, you can
* // explicitly specify the names of the parameters your task function needs
* // in an array, similar to Angular.js dependency injection.
*
* // This still has an advantage over plain `auto`, since the results a task
* // depends on are still spread into arguments.
* async.autoInject({
* //...
* write_file: ['get_data', 'make_folder', function(get_data, make_folder, callback) {
* callback(null, 'filename');
* }],
* email_link: ['write_file', function(write_file, callback) {
* callback(null, {'file':write_file, 'email':'user@example.com'});
* }]
* //...
* }, function(err, results) {
* console.log('err = ', err);
* console.log('email_link = ', results.email_link);
* });
*/
function autoInject(tasks, callback) {
var newTasks = {};
Object.keys(tasks).forEach(key => {
var taskFn = tasks[key];
var params;
var fnIsAsync = isAsync(taskFn);
var hasNoDeps =
(!fnIsAsync && taskFn.length === 1) ||
(fnIsAsync && taskFn.length === 0);
if (Array.isArray(taskFn)) {
params = [...taskFn];
taskFn = params.pop();
newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn);
} else if (hasNoDeps) {
// no dependencies, use the function as-is
newTasks[key] = taskFn;
} else {
params = parseParams(taskFn);
if ((taskFn.length === 0 && !fnIsAsync) && params.length === 0) {
throw new Error("autoInject task functions require explicit parameters.");
}
// remove callback param
if (!fnIsAsync) params.pop();
newTasks[key] = params.concat(newTask);
}
function newTask(results, taskCb) {
var newArgs = params.map(name => results[name]);
newArgs.push(taskCb);
wrapAsync(taskFn)(...newArgs);
}
});
return auto(newTasks, callback);
}
// Simple doubly linked list (https://en.wikipedia.org/wiki/Doubly_linked_list) implementation
// used for queues. This implementation assumes that the node provided by the user can be modified
// to adjust the next and last properties. We implement only the minimal functionality
// for queue support.
class DLL {
constructor() {
this.head = this.tail = null;
this.length = 0;
}
removeLink(node) {
if (node.prev) node.prev.next = node.next;
else this.head = node.next;
if (node.next) node.next.prev = node.prev;
else this.tail = node.prev;
node.prev = node.next = null;
this.length -= 1;
return node;
}
empty () {
while(this.head) this.shift();
return this;
}
insertAfter(node, newNode) {
newNode.prev = node;
newNode.next = node.next;
if (node.next) node.next.prev = newNode;
else this.tail = newNode;
node.next = newNode;
this.length += 1;
}
insertBefore(node, newNode) {
newNode.prev = node.prev;
newNode.next = node;
if (node.prev) node.prev.next = newNode;
else this.head = newNode;
node.prev = newNode;
this.length += 1;
}
unshift(node) {
if (this.head) this.insertBefore(this.head, node);
else setInitial(this, node);
}
push(node) {
if (this.tail) this.insertAfter(this.tail, node);
else setInitial(this, node);
}
shift() {
return this.head && this.removeLink(this.head);
}
pop() {
return this.tail && this.removeLink(this.tail);
}
toArray() {
return [...this]
}
*[Symbol.iterator] () {
var cur = this.head;
while (cur) {
yield cur.data;
cur = cur.next;
}
}
remove (testFn) {
var curr = this.head;
while(curr) {
var {next} = curr;
if (testFn(curr)) {
this.removeLink(curr);
}
curr = next;
}
return this;
}
}
function setInitial(dll, node) {
dll.length = 1;
dll.head = dll.tail = node;
}
const noop = () => {};
function queue(worker, concurrency, payload) {
if (concurrency == null) {
concurrency = 1;
}
else if(concurrency === 0) {
throw new RangeError('Concurrency must not be zero');
}
var _worker = wrapAsync(worker);
var numRunning = 0;
var workersList = [];
var processingScheduled = false;
function _insert(data, insertAtFront, callback) {
if (callback != null && typeof callback !== 'function') {
throw new Error('task callback must be a function');
}
q.started = true;
if (!Array.isArray(data)) {
data = [data];
}
if (data.length === 0 && q.idle()) {
// call drain immediately if there are no tasks
return setImmediate$1(() => q.drain());
}
for (var i = 0, l = data.length; i < l; i++) {
var item = {
data: data[i],
callback: callback || noop
};
if (insertAtFront) {
q._tasks.unshift(item);
} else {
q._tasks.push(item);
}
}
if (!processingScheduled) {
processingScheduled = true;
setImmediate$1(() => {
processingScheduled = false;
q.process();
});
}
}
function _next(tasks) {
return function (err, ...args) {
numRunning -= 1;
for (var i = 0, l = tasks.length; i < l; i++) {
var task = tasks[i];
var index = workersList.indexOf(task);
if (index === 0) {
workersList.shift();
} else if (index > 0) {
workersList.splice(index, 1);
}
task.callback(err, ...args);
if (err != null) {
q.error(err, task.data);
}
}
if (numRunning <= (q.concurrency - q.buffer) ) {
q.unsaturated();
}
if (q.idle()) {
q.drain();
}
q.process();
};
}
var isProcessing = false;
var q = {
_tasks: new DLL(),
*[Symbol.iterator] () {
yield* q._tasks[Symbol.iterator]();
},
concurrency,
payload,
saturated: noop,
unsaturated:noop,
buffer: concurrency / 4,
empty: noop,
drain: noop,
error: noop,
started: false,
paused: false,
push (data, callback) {
_insert(data, false, callback);
},
kill () {
q.drain = noop;
q._tasks.empty();
},
unshift (data, callback) {
_insert(data, true, callback);
},
remove (testFn) {
q._tasks.remove(testFn);
},
process () {
// Avoid trying to start too many processing operations. This can occur
// when callbacks resolve synchronously (#1267).
if (isProcessing) {
return;
}
isProcessing = true;
while(!q.paused && numRunning < q.concurrency && q._tasks.length){
var tasks = [], data = [];
var l = q._tasks.length;
if (q.payload) l = Math.min(l, q.payload);
for (var i = 0; i < l; i++) {
var node = q._tasks.shift();
tasks.push(node);
workersList.push(node);
data.push(node.data);
}
numRunning += 1;
if (q._tasks.length === 0) {
q.empty();
}
if (numRunning === q.concurrency) {
q.saturated();
}
var cb = onlyOnce(_next(tasks));
_worker(data, cb);
}
isProcessing = false;
},
length () {
return q._tasks.length;
},
running () {
return numRunning;
},
workersList () {
return workersList;
},
idle() {
return q._tasks.length + numRunning === 0;
},
pause () {
q.paused = true;
},
resume () {
if (q.paused === false) { return; }
q.paused = false;
setImmediate$1(q.process);