|
|
|
// Copyright (c) 2016 Electric Imp |
|
// This file is licensed under the MIT License |
|
// http://opensource.org/licenses/MIT |
|
|
|
// From: https://github.com/caolan/async |
|
|
|
class async { |
|
|
|
/*********[ Collections ]*********/ |
|
|
|
// each: Applies the function iteratee to each item in arr, in parallel. |
|
// related: eachLimit |
|
static function each(arr, iterator, callback = null) { |
|
return _eachOf(arr, _withoutIndex(iterator), callback); |
|
} |
|
|
|
// eachSeries: Applies the function iteratee to each item in arr, in series. |
|
static function eachSeries(arr, iterator, callback = null) { |
|
return eachOfSeries(arr, _withoutIndex(iterator), callback); |
|
} |
|
|
|
// forEachOf: Like each, except that it iterates over objects, and passes the key as the second argument to the iteratee. |
|
// related: forEachOfLimit |
|
static function forEachOf(obj, iterator, callback = null) { |
|
return _eachOf(obj, iterator, callback); |
|
} |
|
|
|
// forEachOf: Like each, except that it iterates over objects, and passes the key as the second argument to the iteratee. |
|
static function forEachOfSeries(obj, iterator, callback = null) { |
|
return _eachOfSeries(obj, iterator, callback); |
|
} |
|
|
|
// map: Produces a new array of values by mapping each value in arr through the iteratee function. |
|
// related: mapLimit |
|
static function map(arr, iterator, callback = null) { |
|
return _doParallel(_map)(arr, iterator, callback); |
|
} |
|
|
|
// mapSeries: Produces a new array of values by mapping each value in arr through the iteratee function. |
|
static function mapSeries(arr, iterator, callback = null) { |
|
return _doSeries(_map)(arr, iterator, callback); |
|
} |
|
|
|
// filter: Returns a new array of all the values in arr which pass an async truth test |
|
// related: filterLimit |
|
// aliases: select |
|
static function filter(arr, iterator, callback = null) { |
|
return _doParallel(_filter)(arr, iterator, callback); |
|
} |
|
|
|
// filterSeries: Returns a new array of all the values in arr which pass an async truth test |
|
static function filterSeries(arr, iterator, callback = null) { |
|
return _doSeries(_filter)(arr, iterator, callback); |
|
} |
|
|
|
// reject: The opposite of filter. Removes values that pass an async truth test. |
|
// related: rejectLimit |
|
static function reject(arr, iterator, callback = null) { |
|
return _doParallel(_reject)(arr, iterator, callback); |
|
} |
|
|
|
// rejectSeries: The opposite of filter. Removes values that pass an async truth test. |
|
static function rejectSeries(arr, iterator, callback = null) { |
|
return _doSeries(_reject)(arr, iterator, callback); |
|
} |
|
|
|
// reduce: Reduces arr into a single value using an async iteratee to return each successive step. memo is the initial state of the reduction. |
|
// aliases: inject, foldl |
|
static function reduce(arr, memo, iterator, callback) { |
|
_eachOfSeries(arr, function(x, i, callback) { |
|
iterator(memo, x, function(err, v) { |
|
memo = v; |
|
callback(err); |
|
}); |
|
}.bindenv(this), function(err) { |
|
callback(err, memo); |
|
}.bindenv(this)); |
|
} |
|
|
|
// reduceRight: Same as reduce, only operates on arr in reverse order. |
|
// aliases: foldr |
|
static function reduceRight(arr, memo, iterator, callback = null) { |
|
arr.reverse(); |
|
return reduce(arr, memo, iterator, callback); |
|
} |
|
|
|
// detect: Returns the first value in arr that passes an async truth test. |
|
// related: detectLimit |
|
static function detect(arr, iterator, callback = null) { |
|
return _createTester(_eachOf, _identity, _findGetResult)(arr, null, iterator, callback); |
|
} |
|
|
|
// detect: Returns the first value in arr that passes an async truth test. |
|
static function detectSeries(arr, iterator, callback = null) { |
|
return _createTester(_eachOfSeries, _identity, _findGetResult)(arr, null, iterator, callback); |
|
} |
|
|
|
// some: Returns true if at least one element in the arr satisfies an async test. If any iteratee call returns true, the main callback is immediately called. |
|
// related: someLimit |
|
// aliases: any |
|
static function some(arr, iterator, callback = null) { |
|
return _createTester(_eachOf, _boolean, _identity)(arr, null, iterator, callback); |
|
} |
|
|
|
// every: Returns true if every element in arr satisfies an async test. If any iteratee call returns false, the main callback is immediately called. |
|
// related: everyLimit |
|
// aliases: all |
|
static function every(arr, iterator, callback = null) { |
|
return _createTester(_eachOf, _notId, _notId)(arr, null, iterator, callback); |
|
} |
|
|
|
// concat: Applies iteratee to each item in arr, concatenating the results. Returns the concatenated list. |
|
static function concat(arr, iterator, callback = null) { |
|
return _doParallel(_concat)(arr, iterator, callback); |
|
} |
|
|
|
// concatSeries: Applies iteratee to each item in arr, concatenating the results. Returns the concatenated list. |
|
static function concatSeries(arr, iterator, callback = null) { |
|
return _doSeries(_concat)(arr, iterator, callback); |
|
} |
|
|
|
// sortBy: Sorts a list by the results of running each arr value through an async iteratee. |
|
static function sortBy(arr, iterator, callback = null) { |
|
|
|
function comparator(left, right) { |
|
local a = left.criteria, b = right.criteria; |
|
return a < b ? -1 : a > b ? 1 : 0; |
|
} |
|
|
|
map(arr, function(x, cb) { |
|
iterator(x, function(err, criteria) { |
|
if (err) return cb(err); |
|
cb(null, {"value": x, "criteria": criteria}); |
|
}); |
|
}.bindenv(this), function(err, results) { |
|
if (err) return cb(err); |
|
results.sort(comparator); |
|
callback(null, _arrayMap(results, _property("value"))); |
|
}.bindenv(this)); |
|
|
|
} |
|
|
|
|
|
/*********[ Control Flow ]*********/ |
|
|
|
// series: Run the functions in the tasks array in series, each one running once the previous function has completed. |
|
static function series(tasks, callback = null) { |
|
return _parallel(_eachOfSeries, tasks, callback); |
|
} |
|
|
|
// parallel: Run the tasks array of functions in parallel, without waiting until the previous function has completed. |
|
// related: parallelLimit |
|
static function parallel(tasks, callback = null) { |
|
return _parallel(_eachOf, tasks, callback); |
|
} |
|
|
|
// whilst: Repeatedly call fn, while test returns true. Calls callback when stopped, or an error occurs. |
|
// related: doWhilst |
|
static function whilst(test, iterator, callback = null) { |
|
|
|
callback = callback || _noop; |
|
if (!test()) return callback(null); |
|
|
|
local next; |
|
next = _rest(function(err, args) { |
|
if (err) return callback(err); |
|
if (test()) return iterator(next); |
|
args.insert(0, this); |
|
args.insert(1, null); |
|
callback.acall(args); |
|
}); |
|
iterator(next); |
|
|
|
} |
|
|
|
// until: Repeatedly call fn until test returns true. The inverse of whilst. |
|
// related: doUntil |
|
static function until(test, iterator, callback = null) { |
|
return whilst(function() { |
|
if (vargv.len() == 0 || vargv[0] != this) vargv.insert(0, this); |
|
return !test.acall(vargv); |
|
}, iterator, callback); |
|
} |
|
|
|
// during: Like whilst, except the test is an asynchronous function that is passed a callback in the form of function(err, truth). |
|
// related: doDuring |
|
static function during(test, iterator, callback = null) { |
|
|
|
callback = callback || _noop; |
|
|
|
local next, check; |
|
|
|
next = _rest(function(err, args) { |
|
if (err) return callback(err); |
|
test(check); |
|
}); |
|
|
|
check = function(err, truth) { |
|
if (err) return callback(err); |
|
if (!truth) return callback(null); |
|
iterator(next); |
|
}; |
|
|
|
test(check); |
|
} |
|
|
|
// forever: Calls the asynchronous function fn with a callback parameter that allows it to call itself again, in series, indefinitely. |
|
static function forever(iterator, errback = null) { |
|
local done = _once(errback || _noop); |
|
local task = ensureAsync(iterator); |
|
local next; |
|
|
|
next = function(err = null) { |
|
if (err) return done(err); |
|
task(next); |
|
} |
|
next(); |
|
} |
|
|
|
// waterfall: Runs the tasks array of functions in series, each passing their results to the next in the array. |
|
static function waterfall(tasks, callback = null) { |
|
|
|
callback = _once(callback || _noop); |
|
if (!tasks.len()) return callback(); |
|
|
|
local wrapIterator; |
|
wrapIterator = function(iterator) { |
|
return _rest(function(err, args) { |
|
args.insert(0, this); |
|
if (err) { |
|
args.insert(1, err); |
|
callback.acall(args); |
|
} else { |
|
local next = iterator.next(); |
|
if (next) { |
|
args.push(wrapIterator(next)); |
|
} else { |
|
args.push(callback); |
|
} |
|
ensureAsync(iterator.fn).acall(args); |
|
} |
|
}); |
|
} |
|
wrapIterator(iterator(tasks))(); |
|
|
|
} |
|
|
|
// compose: Creates a function which is a composition of the passed asynchronous functions. Each function consumes the return value of the function that follows. |
|
static function compose(...) { |
|
vargv.reverse(); |
|
vargv.insert(0, this); |
|
return seq.acall(vargv); |
|
} |
|
|
|
// seq: Version of the compose function that is more natural to read. Each function consumes the return value of the previous function. |
|
static function seq(...) { |
|
|
|
local fns = vargv; |
|
return function(...) { |
|
|
|
local args = vargv; |
|
local cb = args.len() > 0 ? args[args.len() - 1] : null; |
|
if (typeof cb == "function") { |
|
args.pop(); |
|
} else { |
|
cb = _noop; |
|
} |
|
|
|
reduce(fns, args, |
|
|
|
function(newargs, fn, cb) { |
|
local next = _rest(function(err, nextargs) { |
|
cb(err, nextargs); |
|
}) |
|
newargs.insert(0, this); |
|
newargs.push(next); |
|
fn.acall(newargs); |
|
}.bindenv(this), |
|
|
|
function(err, results) { |
|
results.insert(0, this); |
|
results.insert(1, err); |
|
cb.acall(results); |
|
}.bindenv(this) |
|
); |
|
}.bindenv(this); |
|
|
|
} |
|
|
|
// applyEach: Applies the provided arguments to each function in the array, calling callback after all functions have completed. |
|
// related: applyEachSeries |
|
static function applyEach(...) { |
|
vargv.insert(0, this); |
|
return _applyEach(_eachOf).acall(vargv); |
|
} |
|
|
|
|
|
/*********[ Utils ]*********/ |
|
|
|
// nextTick: Calls callback on a later loop around the event loop. |
|
// aliases: setImmediate |
|
static function nextTick(callback) { |
|
imp.wakeup(0, callback); |
|
} |
|
|
|
|
|
// ensureAsync: Wrap an async function and ensure it calls its callback on a later tick of the event loop. |
|
static function ensureAsync(callback) { |
|
return function(...) { |
|
vargv.insert(0, this); |
|
imp.wakeup(0, function() { |
|
callback.acall(vargv); |
|
}); |
|
} |
|
} |
|
|
|
|
|
// asyncify: Take a sync function and make it async, passing its return value to a callback. Handles Promises too. |
|
static function asyncify(fn) { |
|
|
|
return function(...) { |
|
local args = vargv; |
|
local callback = args.pop(); |
|
local result; |
|
try { |
|
args.insert(0, this); |
|
result = fn.acall(args); |
|
} catch (e) { |
|
return callback(e); |
|
} |
|
|
|
if ("then" in result && typeof result.then == "function") { |
|
// if result is Promise object |
|
result.then(function(res) { |
|
callback(null, res); |
|
}, function(err) { |
|
callback(err); |
|
}); |
|
} else { |
|
callback(null, result); |
|
} |
|
} |
|
|
|
} |
|
|
|
|
|
// iterator: Creates an iterator function which calls the next function in the tasks array, returning a continuation to call the next one after that. |
|
static function iterator(tasks) { |
|
|
|
local makeCallback; |
|
makeCallback = function(index) { |
|
|
|
local cb = {} |
|
cb.fn <- function(...) { |
|
if (tasks.len()) { |
|
vargv.insert(0, this); |
|
tasks[index].acall(vargv); |
|
} |
|
return cb.next(); |
|
} |
|
cb.next <- function() { |
|
return (index < tasks.len() - 1) ? makeCallback(index + 1) : null; |
|
}; |
|
return cb; |
|
} |
|
return makeCallback(0); |
|
|
|
} |
|
|
|
|
|
// times: Calls the iteratee function n times, and accumulates results in the same manner you would use with map. |
|
// related: timesSeries, timesLimit |
|
static function times(count, iterator, callback = null) { |
|
map(_range(0, count, 1), iterator, callback); |
|
} |
|
|
|
|
|
// constant: Returns a function that when called, calls-back with the values provided. |
|
static function constant(...) { |
|
local args = vargv; |
|
args.insert(0, this); |
|
args.insert(1, null); |
|
return function (cb) { |
|
return cb.acall(args); |
|
}; |
|
} |
|
|
|
|
|
|
|
/*********[ Unsupported ]*********/ |
|
|
|
/* |
|
|
|
// apply: Creates a continuation function with some arguments already applied. |
|
static function apply(fn, ...) { |
|
|
|
} |
|
|
|
// queue: Creates a queue object with the specified concurrency. Tasks added to the queue are processed in parallel (up to the concurrency limit). |
|
static function queue(worker, concurrency = 1) { |
|
|
|
} |
|
|
|
// priorityQueue: The same as queue only tasks are assigned a priority and completed in ascending priority order. |
|
static function priorityQueue(worker, concurrency) { |
|
|
|
} |
|
|
|
// cargo: The same as queue only tasks are assigned a priority and completed in ascending priority order. |
|
static function cargo(worker, payload = null) { |
|
|
|
} |
|
|
|
// auto: Determines the best order for running the functions in tasks, based on their requirements. |
|
static function auto(tasks, concurrency = null, callback = null) { |
|
|
|
} |
|
|
|
// log: Logs the result of an async function to the console. |
|
// related: dir |
|
static function log(fn, ...) { |
|
|
|
} |
|
|
|
// retry: Attempts to get a successful response from task no more than times times before returning an error. |
|
static function retry(opts, task, callback = null) { |
|
|
|
} |
|
|
|
// memoize |
|
// unmemoize |
|
|
|
*/ |
|
|
|
|
|
/*********[ Internal ]*********/ |
|
|
|
|
|
static function _eachOf(object, iterator, callback = null) { |
|
|
|
local callback = _once(callback || _noop); |
|
local object = object || {}; |
|
local completed = 0, key = null; |
|
|
|
function done(err = null) { |
|
completed--; |
|
// Check key is null in case iterator isn't exhausted and done resolved synchronously. |
|
if (err) { |
|
callback(err); |
|
} else if (key == null && completed <= 0) { |
|
callback(null); |
|
} |
|
} |
|
|
|
foreach (_key,val in object) { |
|
key = _key; // Copy to the larger scoped key |
|
completed++; |
|
iterator(val, key, _once(done)); |
|
} |
|
key = null; |
|
|
|
if (completed == 0) callback(null); |
|
|
|
} |
|
|
|
|
|
static function _eachOfSeries(obj, iterator, callback) { |
|
|
|
local callback = _once(callback || _noop); |
|
local obj = obj || []; |
|
local nextKey = _keyIterator(obj); |
|
local key = nextKey(); |
|
|
|
local iterate; |
|
iterate = function() { |
|
local sync = true; |
|
if (key == null) { |
|
return callback(null); |
|
} |
|
iterator(obj[key], key, _once(function(err = null) { |
|
if (err) { |
|
callback(err); |
|
} else { |
|
key = nextKey(); |
|
if (key == null) { |
|
return callback(null); |
|
} else { |
|
if (sync) { |
|
nextTick(iterate); |
|
} else { |
|
iterate(); |
|
} |
|
} |
|
} |
|
})); |
|
sync = false; |
|
}.bindenv(this); |
|
|
|
iterate(); |
|
|
|
} |
|
|
|
|
|
static function _applyEach(eachfn) { |
|
return function(fns, ...) { |
|
|
|
local go = function(...) { |
|
|
|
local args = vargv; |
|
local callback = args.len() > 0 ? args.pop() : null; |
|
if (typeof callback != "function") { |
|
callback = _noop; |
|
} |
|
|
|
args.insert(0, this); |
|
args.push(null); |
|
|
|
return eachfn( fns, |
|
function (fn, _, cb) { |
|
args[args.len() - 1] = cb; |
|
fn.acall(args); |
|
}, |
|
callback |
|
); |
|
}; |
|
|
|
if (vargv.len()) { |
|
vargv.insert(0, this); |
|
return go.acall(vargv); |
|
} else { |
|
return go; |
|
} |
|
} |
|
|
|
} |
|
|
|
|
|
static function _before(n, fn) { |
|
|
|
local result = null; |
|
return function(...) { |
|
if (--n > 0) { |
|
vargv.insert(0, this); |
|
result = fn.acall(vargv); |
|
} |
|
if (n <= 1) { |
|
fn = null; |
|
} |
|
return result; |
|
} |
|
} |
|
|
|
|
|
static function _once(fn) { |
|
return _before(2, fn) |
|
} |
|
|
|
|
|
static function _noop(...) { |
|
return null; |
|
} |
|
|
|
|
|
static function _withoutIndex(iterator) { |
|
return function(value, index, callback) { |
|
return iterator(value, callback); |
|
} |
|
} |
|
|
|
|
|
static function _doParallel(fn) { |
|
return function(obj, iterator, callback) { |
|
return fn(_eachOf, obj, iterator, callback); |
|
} |
|
} |
|
|
|
|
|
static function _doSeries(fn) { |
|
return function (obj, iterator, callback) { |
|
return fn(_eachOfSeries, obj, iterator, callback); |
|
}; |
|
} |
|
|
|
|
|
static function _map(eachfn, arr, iterator, callback) { |
|
local callback = _once(callback || _noop); |
|
local arr = arr || []; |
|
local results = (typeof arr == "array") ? array(arr.len()) : {}; |
|
eachfn(arr, function(value, index, callback) { |
|
iterator(value, function(err, v) { |
|
if (typeof results == "array") { |
|
results[index] = v; |
|
} else { |
|
results[index] <- v; |
|
} |
|
callback(err); |
|
}); |
|
}.bindenv(this), function(err) { |
|
callback(err, results); |
|
}.bindenv(this)); |
|
} |
|
|
|
|
|
static function _filter(eachfn, arr, iterator, callback) { |
|
local results = []; |
|
eachfn(arr, function(x, index, callback) { |
|
iterator(x, function(err, v) { |
|
if (err) { |
|
callback(err); |
|
} else { |
|
if (v) { |
|
results.push({"index": index, "value": x}); |
|
} |
|
callback(); |
|
} |
|
}); |
|
}.bindenv(this), function(err) { |
|
if (err) { |
|
callback(err); |
|
} else { |
|
results.sort(function(a, b) { |
|
return a.index - b.index; |
|
}) |
|
callback(null, _arrayMap(results, _property("value"))); |
|
} |
|
}.bindenv(this)); |
|
} |
|
|
|
|
|
static function _reject(eachfn, arr, iterator, callback) { |
|
_filter(eachfn, arr, function(value, cb) { |
|
iterator(value, function(err, v) { |
|
if (err) { |
|
cb(err); |
|
} else { |
|
cb(null, !v); |
|
} |
|
}.bindenv(this)); |
|
}.bindenv(this), callback); |
|
} |
|
|
|
|
|
static function _arrayMap(arr, iteratee) { |
|
local result = array(arr.len()); |
|
foreach (key,val in arr) { |
|
result[key] = iteratee(val); |
|
} |
|
return result; |
|
} |
|
|
|
|
|
static function _concat(eachfn, arr, fn, callback) { |
|
local result = []; |
|
eachfn(arr, function(x, index, cb) { |
|
fn(x, function(err, y) { |
|
result.extend(y || []); |
|
cb(err); |
|
}); |
|
}.bindenv(this), function(err) { |
|
callback(err, result); |
|
}.bindenv(this)); |
|
} |
|
|
|
|
|
static function _parallel(eachfn, tasks, callback) { |
|
callback = callback || _noop; |
|
local results = (typeof tasks == "array") ? array(tasks.len()) : {}; |
|
|
|
eachfn(tasks, function(task, key, callback) { |
|
task(_rest(function(err = null, args = null) { |
|
|
|
if (args.len() == 0) args = null; |
|
else if (args.len() == 1) args = args[0]; |
|
|
|
if (typeof results == "array") { |
|
results[key] = args; |
|
} else { |
|
results[key] <- args; |
|
} |
|
callback(err); |
|
})); |
|
}.bindenv(this), function(err) { |
|
callback(err, results); |
|
}.bindenv(this)); |
|
} |
|
|
|
|
|
static function _property(key) { |
|
return function(object) { |
|
return (object == null) || !(key in object) ? null : object[key]; |
|
}; |
|
} |
|
|
|
|
|
static function _identity(obj, ...) { |
|
return obj; |
|
} |
|
|
|
|
|
static function _boolean(val, ...) { |
|
return !!val; |
|
} |
|
|
|
|
|
static function _notId(val, ...) { |
|
return !val; |
|
} |
|
|
|
|
|
static function _findGetResult(v, x) { |
|
return x; |
|
} |
|
|
|
|
|
static function _nativeMax(a, b) { |
|
return a > b ? a : b; |
|
} |
|
|
|
|
|
static function _keys(obj) { |
|
local results = []; |
|
foreach (key, val in obj) { |
|
results.push(key); |
|
} |
|
return results; |
|
} |
|
|
|
|
|
static function _keyIterator(coll) { |
|
local i = -1; |
|
local len; |
|
if (typeof coll == "array") { |
|
len = coll.len(); |
|
return function /* next */ () { |
|
i++; |
|
return i < len ? i : null; |
|
}; |
|
} else { |
|
local okeys = _keys(coll); |
|
len = okeys.len(); |
|
return function /* next */ () { |
|
i++; |
|
return i < len ? okeys[i] : null; |
|
}; |
|
} |
|
} |
|
|
|
|
|
static function _createTester(eachfn, check, getResult) { |
|
|
|
return function(arr, limit, iterator, cb) { |
|
|
|
function done(err) { |
|
if (cb) { |
|
if (err) { |
|
cb(err); |
|
} else { |
|
cb(null, getResult(false, null)); |
|
} |
|
} |
|
} |
|
|
|
function iteratee(x, _, callback) { |
|
if (!cb) return callback(); |
|
iterator(x, function(err, v) { |
|
if (cb) { |
|
if (err) { |
|
cb(err); |
|
cb = iterator = false; |
|
} else if (check(v)) { |
|
cb(null, getResult(true, x)); |
|
cb = iterator = false; |
|
} |
|
} |
|
callback(); |
|
}.bindenv(this)); |
|
} |
|
|
|
if (limit == null) { |
|
eachfn(arr, iteratee, done); |
|
} else { |
|
eachfn(arr, limit, iteratee, done); |
|
} |
|
}; |
|
} |
|
|
|
|
|
static function _rest(func) { |
|
|
|
return function(...) { |
|
|
|
local first, rest; |
|
if (vargv.len() > 0) { |
|
first = vargv[0]; |
|
vargv.remove(0); |
|
rest = vargv; |
|
} else { |
|
first = null; |
|
rest = []; |
|
} |
|
|
|
return func(first, rest); |
|
}.bindenv(this); |
|
} |
|
|
|
|
|
static function _range(start, end, step = 1, fromRight = false) { |
|
local index = -1, |
|
length = _nativeMax(math.ceil((end - start) / (step || 1)), 0), |
|
result = array(length); |
|
|
|
while (length--) { |
|
result[fromRight ? length : ++index] = start; |
|
start += step; |
|
} |
|
return result; |
|
} |
|
|
|
} |
|
|