insync
Version:
Higher-order functions and common patterns for asynchronous code. Node specific port of async.
856 lines (626 loc) • 22.3 kB
JavaScript
;
// Load modules
var Collection = require('./collection');
var Util = require('./util');
// Declare internals
var internals = {
DEFAULT_RETRIES: 5,
DEFAULT_INTERVAL: 0
};
internals._applyEach = function (eachfn, fns) {
for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
args[_key - 2] = arguments[_key];
}
var go = function go() {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
var self = this;
var callback = args.pop();
return eachfn(fns, function (fn, cb) {
fn.apply(self, args.concat([cb]));
}, callback);
};
if (args.length > 0) {
return go.apply(this, args);
}
return go;
};
internals._parallel = function (eachfn, tasks, callback) {
callback = callback || Util.noop;
if (Array.isArray(tasks)) {
eachfn.map(tasks, function (fn, callback) {
fn(function (err) {
for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
args[_key3 - 1] = arguments[_key3];
}
if (args.length <= 1) {
args = args[0];
}
callback.call(null, err, args);
});
}, callback);
} else {
(function () {
var results = {};
eachfn.each(Object.keys(tasks), function (k, callback) {
tasks[k](function (err) {
for (var _len4 = arguments.length, args = Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
args[_key4 - 1] = arguments[_key4];
}
if (args.length <= 1) {
args = args[0];
}
results[k] = args;
callback(err);
});
}, function (err) {
callback(err, results);
});
})();
}
};
module.exports.series = function (tasks, callback) {
callback = callback || Util.noop;
if (Array.isArray(tasks)) {
Collection.mapSeries(tasks, function (fn, callback) {
fn(function (err) {
for (var _len5 = arguments.length, args = Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) {
args[_key5 - 1] = arguments[_key5];
}
if (args.length <= 1) {
args = args[0];
}
callback.call(null, err, args);
});
}, callback);
} else {
(function () {
var results = {};
Collection.eachSeries(Object.keys(tasks), function (k, callback) {
tasks[k](function (err) {
for (var _len6 = arguments.length, args = Array(_len6 > 1 ? _len6 - 1 : 0), _key6 = 1; _key6 < _len6; _key6++) {
args[_key6 - 1] = arguments[_key6];
}
if (args.length <= 1) {
args = args[0];
}
results[k] = args;
callback(err);
});
}, function (err) {
callback(err, results);
});
})();
}
};
module.exports.parallel = function (tasks, callback) {
internals._parallel({
map: Collection.map,
each: Collection.each
}, tasks, callback);
};
module.exports.parallelLimit = function (tasks, limit, callback) {
internals._parallel({
map: Collection._mapLimit(limit),
each: Collection._eachLimit(limit)
}, tasks, callback);
};
module.exports.whilst = function (test, iterator, callback) {
if (test()) {
iterator(function (err) {
if (err) {
return callback(err);
}
module.exports.whilst(test, iterator, callback);
});
} else {
callback();
}
};
module.exports.doWhilst = function (iterator, test, callback) {
iterator(function (err) {
if (err) {
return callback(err);
}
for (var _len7 = arguments.length, args = Array(_len7 > 1 ? _len7 - 1 : 0), _key7 = 1; _key7 < _len7; _key7++) {
args[_key7 - 1] = arguments[_key7];
}
if (test.apply(null, args)) {
module.exports.doWhilst(iterator, test, callback);
} else {
callback();
}
});
};
module.exports.until = function (test, iterator, callback) {
if (!test()) {
iterator(function (err) {
if (err) {
return callback(err);
}
module.exports.until(test, iterator, callback);
});
} else {
callback();
}
};
module.exports.doUntil = function (iterator, test, callback) {
iterator(function (err) {
if (err) {
return callback(err);
}
for (var _len8 = arguments.length, args = Array(_len8 > 1 ? _len8 - 1 : 0), _key8 = 1; _key8 < _len8; _key8++) {
args[_key8 - 1] = arguments[_key8];
}
if (!test.apply(null, args)) {
module.exports.doUntil(iterator, test, callback);
} else {
callback();
}
});
};
module.exports.forever = function (fn, callback) {
var next = function next(err) {
if (err) {
if (callback) {
return callback(err);
}
throw err;
}
fn(next);
};
next();
};
module.exports.waterfall = function (tasks, callback) {
callback = callback || Util.noop;
if (!Array.isArray(tasks)) {
var err = new Error('First argument to waterfall must be an array of functions');
return callback(err);
}
if (!tasks.length) {
return callback();
}
var size = tasks.length;
var called = false;
var args = [];
var iterate = function iterate(completed) {
var func = tasks[completed];
var done = function done(err) {
if (called) {
throw new Error('Callback was already called');
}
called = true;
if (err) {
return callback.apply(undefined, arguments);
}
++completed;
if (completed === size) {
return callback.apply(undefined, arguments);
}
var l = arguments.length;
args = Array(l > 1 ? l - 1 : 0);
for (var i = 1; i < l; ++i) {
args[i - 1] = arguments[i];
}
iterate(completed);
};
called = false;
args.push(done);
func.apply(null, args);
};
iterate(0);
};
module.exports.compose = function () {
for (var _len9 = arguments.length, args = Array(_len9), _key9 = 0; _key9 < _len9; _key9++) {
args[_key9] = arguments[_key9];
}
return module.exports.seq.apply(null, Array.prototype.reverse.call(args));
};
module.exports.seq = function () {
for (var _len10 = arguments.length, fns = Array(_len10), _key10 = 0; _key10 < _len10; _key10++) {
fns[_key10] = arguments[_key10];
}
return function () {
var self = this;
for (var _len11 = arguments.length, args = Array(_len11), _key11 = 0; _key11 < _len11; _key11++) {
args[_key11] = arguments[_key11];
}
var callback = args.pop();
Collection.reduce(fns, args, function (newargs, fn, cb) {
fn.apply(self, newargs.concat([function (err) {
for (var _len12 = arguments.length, nextargs = Array(_len12 > 1 ? _len12 - 1 : 0), _key12 = 1; _key12 < _len12; _key12++) {
nextargs[_key12 - 1] = arguments[_key12];
}
cb(err, nextargs);
}]));
}, function (err, results) {
callback.apply(self, [err].concat(results));
});
};
};
module.exports.applyEach = Collection.doParallel(internals._applyEach);
module.exports.applyEachSeries = Collection.doSeries(internals._applyEach);
internals._queue = function (worker, concurrency, payload) {
if (concurrency === undefined) {
concurrency = 1;
} else if (concurrency < 1 || concurrency >>> 0 !== concurrency) {
throw new RangeError('Concurrency must be a positive integer');
}
var _insert = function _insert(q, data, pos, callback) {
if (callback !== undefined && typeof callback !== 'function') {
throw new TypeError('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(q.drain);
}
for (var i = 0, il = data.length; i < il; ++i) {
var item = {
data: data[i],
callback: typeof callback === 'function' ? callback : Util.noop
};
if (pos) {
q.tasks.unshift(item);
} else {
q.tasks.push(item);
}
if (q.tasks.length === q.concurrency) {
q.saturated();
}
}
setImmediate(q.process);
};
var next = function next(q, tasks) {
return function () {
workers--;
for (var _len13 = arguments.length, args = Array(_len13), _key13 = 0; _key13 < _len13; _key13++) {
args[_key13] = arguments[_key13];
}
for (var i = 0, il = tasks.length; i < il; ++i) {
var task = tasks[i];
task.callback.apply(task, args);
}
if (q.tasks.length + workers === 0) {
q.drain();
}
q.process();
};
};
var workers = 0;
var q = {
tasks: [],
concurrency: concurrency,
saturated: Util.noop,
empty: Util.noop,
drain: Util.noop,
started: false,
paused: false,
push: function push(data, callback) {
_insert(q, data, false, callback);
},
kill: function kill() {
q.drain = Util.noop;
q.tasks = [];
},
unshift: function unshift(data, callback) {
_insert(q, data, true, callback);
},
process: function process() {
while (!q.paused && workers < q.concurrency && q.tasks.length) {
var tasks = payload ? q.tasks.splice(0, payload) : q.tasks.splice(0, q.tasks.length);
var _length = tasks.length;
var data = new Array(_length);
for (var i = 0; i < _length; ++i) {
data[i] = tasks[i].data;
}
if (q.tasks.length === 0) {
q.empty();
}
var cb = Util.onlyOnce(next(q, tasks));
workers++;
worker(data, cb);
}
},
length: function length() {
return q.tasks.length;
},
running: function running() {
return workers;
},
idle: function idle() {
return q.tasks.length + workers === 0;
},
pause: function pause() {
q.paused = true;
},
resume: function resume() {
if (q.paused === false) {
return;
}
q.paused = false;
// Need to call q.process once per concurrent
// worker to preserve full concurrency after pause
var resumeCount = Math.min(q.concurrency, q.tasks.length);
for (var w = 1; w <= resumeCount; ++w) {
setImmediate(q.process);
}
}
};
return q;
};
module.exports.queue = function (worker, concurrency) {
return internals._queue(function (items, cb) {
worker(items[0], cb);
}, concurrency, 1);
};
module.exports.priorityQueue = function (worker, concurrency) {
var _compareTasks = function _compareTasks(a, b) {
return a.priority - b.priority;
};
var _binarySearch = function _binarySearch(sequence, item, compare) {
var beg = -1;
var end = sequence.length - 1;
while (beg < end) {
var mid = beg + (end - beg + 1 >>> 1);
if (compare(item, sequence[mid]) >= 0) {
beg = mid;
} else {
end = mid - 1;
}
}
return beg;
};
var _insert = function _insert(q, data, priority, callback) {
if (callback !== undefined && typeof callback !== 'function') {
throw new TypeError('Callback must be a function');
}
q.started = true;
if (!Array.isArray(data)) {
data = [data];
}
if (data.length === 0) {
// Call drain immediately if there are no tasks
return setImmediate(q.drain);
}
for (var i = 0, il = data.length; i < il; ++i) {
var task = data[i];
var item = {
data: task,
priority: priority,
callback: typeof callback === 'function' ? callback : Util.noop
};
q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item);
if (q.tasks.length === q.concurrency) {
q.saturated();
}
setImmediate(q.process);
}
};
// Start with a normal queue
var q = module.exports.queue(worker, concurrency);
// Override push to accept second parameter representing priority
q.push = function (data, priority, callback) {
_insert(q, data, priority, callback);
};
// Remove unshift function
delete q.unshift;
return q;
};
module.exports.cargo = function (worker, payload) {
return internals._queue(worker, 1, payload);
};
module.exports.auto = function (tasks, callback) {
callback = callback || Util.noop;
var keys = Object.keys(tasks);
var remainingTasks = keys.length;
if (!remainingTasks) {
return callback();
}
var results = {};
var listeners = [];
var addListener = function addListener(fn) {
listeners.unshift(fn);
};
var removeListener = function removeListener(fn) {
var index = listeners.indexOf(fn);
listeners.splice(index, 1);
};
var taskComplete = function taskComplete() {
remainingTasks--;
var copy = listeners.slice(0);
for (var i = 0, il = copy.length; i < il; ++i) {
var fn = copy[i];
fn();
}
};
var taskCallbackGenerator = function taskCallbackGenerator(key) {
return function taskCallback(err) {
for (var _len14 = arguments.length, args = Array(_len14 > 1 ? _len14 - 1 : 0), _key14 = 1; _key14 < _len14; _key14++) {
args[_key14 - 1] = arguments[_key14];
}
if (args.length <= 1) {
args = args[0];
}
if (err) {
var safeResults = {};
var _keys = Object.keys(results);
for (var i = 0, il = _keys.length; i < il; ++i) {
var rkey = _keys[i];
safeResults[rkey] = results[rkey];
}
safeResults[key] = args;
callback(err, safeResults);
// Stop subsequent errors hitting callback multiple times
callback = Util.noop;
} else {
results[key] = args;
setImmediate(taskComplete);
}
};
};
var taskReadyGenerator = function taskReadyGenerator(key, requires) {
return function taskReady() {
// Note: Originally this was ported with the
// !results.hasOwnProperty(k) check first in the
// return statement. However, coverage was never achieved
// for this line as I have yet to see a case where this
// was false. A test would be appreciated, otherwise this
// return statement should be refactored.
var run = true;
var i = requires.length;
while (i--) {
run = results.hasOwnProperty(requires[i]);
if (!run) {
break;
}
}
return run && !(key in results);
};
};
var taskListenerGenerator = function taskListenerGenerator(ready, task, taskCallback) {
var listener = function taskListener() {
if (ready()) {
removeListener(listener);
task[task.length - 1](taskCallback, results);
}
};
return listener;
};
addListener(function () {
if (!remainingTasks) {
var theCallback = callback;
// Prevent final callback from calling itself if it errors
callback = Util.noop;
theCallback(null, results);
}
});
for (var i = 0, il = keys.length; i < il; ++i) {
var k = keys[i];
var task = Array.isArray(tasks[k]) ? tasks[k] : [tasks[k]];
var taskCallback = taskCallbackGenerator(k);
var requires = task.slice(0, Math.abs(task.length - 1));
var ready = taskReadyGenerator(k, requires);
var listener = taskListenerGenerator(ready, task, taskCallback);
// Prevent deadlocks
var len = requires.length;
while (len--) {
var dep = tasks[requires[len]];
if (!dep) {
throw new Error('Has inexistant dependency');
}
if (Array.isArray(dep) && dep.indexOf(k) !== -1) {
throw new Error('Has cyclic dependencies');
}
}
if (ready()) {
task[task.length - 1](taskCallback, results);
} else {
addListener(listener);
}
}
};
module.exports.retry = function (times, task, callback) {
var attempts = [];
var interval = undefined;
// Parse arguments
if (typeof times === 'function') {
// retry(task[, callback])
callback = task;
task = times;
times = internals.DEFAULT_RETRIES;
interval = internals.DEFAULT_INTERVAL;
} else if (typeof times === 'number') {
// retry(number, task[, callback])
times = times >>> 0 || internals.DEFAULT_RETRIES;
interval = internals.DEFAULT_INTERVAL;
} else if (times !== null && typeof times === 'object') {
// retry(object, task[, callback])
interval = typeof times.interval === 'number' ? times.interval : internals.DEFAULT_INTERVAL;
times = times.times >>> 0 || internals.DEFAULT_RETRIES;
} else {
throw TypeError('Retry expects number or object');
}
var wrappedTask = function wrappedTask(wrappedCallback, wrappedResults) {
var retryAttempt = function retryAttempt(task, finalAttempt) {
return function (seriesCallback) {
task(function (err, result) {
seriesCallback(!err || finalAttempt, {
err: err,
result: result
});
}, wrappedResults);
};
};
var retryTimeout = function retryTimeout(seriesCallback) {
setTimeout(seriesCallback, interval);
};
while (times) {
var finalAttempt = --times === 0;
attempts.push(retryAttempt(task, finalAttempt));
if (!finalAttempt && interval > 0) {
attempts.push(retryTimeout);
}
}
module.exports.series(attempts, function (done, data) {
data = data[data.length - 1];
(wrappedCallback || callback)(data.err, data.result);
});
};
// If a callback is passed, run this as a control flow
return callback ? wrappedTask() : wrappedTask;
};
module.exports.iterator = function (tasks) {
var makeCallback = function makeCallback(index) {
var fn = function fn() {
if (tasks.length) {
for (var _len15 = arguments.length, args = Array(_len15), _key15 = 0; _key15 < _len15; _key15++) {
args[_key15] = arguments[_key15];
}
tasks[index].apply(null, args);
}
return fn.next();
};
fn.next = function () {
return index < tasks.length - 1 ? makeCallback(index + 1) : null;
};
return fn;
};
return makeCallback(0);
};
module.exports.apply = function (fn) {
for (var _len16 = arguments.length, args = Array(_len16 > 1 ? _len16 - 1 : 0), _key16 = 1; _key16 < _len16; _key16++) {
args[_key16 - 1] = arguments[_key16];
}
return function () {
for (var _len17 = arguments.length, innerArgs = Array(_len17), _key17 = 0; _key17 < _len17; _key17++) {
innerArgs[_key17] = arguments[_key17];
}
return fn.apply(null, args.concat(innerArgs));
};
};
module.exports.times = function (count, iterator, callback) {
var counter = [];
for (var i = 0; i < count; ++i) {
counter.push(i);
}
return Collection.map(counter, iterator, callback);
};
module.exports.timesSeries = function (count, iterator, callback) {
var counter = [];
for (var i = 0; i < count; ++i) {
counter.push(i);
}
return Collection.mapSeries(counter, iterator, callback);
};
module.exports.timesLimit = function (count, limit, iterator, callback) {
var counter = [];
for (var i = 0; i < count; ++i) {
counter.push(i);
}
return Collection.mapLimit(counter, limit, iterator, callback);
};