UNPKG

insync

Version:

Higher-order functions and common patterns for asynchronous code. Node specific port of async.

856 lines (626 loc) 22.3 kB
'use strict'; // 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); };