Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
async/lib/async.js /
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
807 lines (737 sloc)
24.4 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /*global setTimeout: false, console: false */ | |
| (function () { | |
| var async = {}; | |
| // global on the server, window in the browser | |
| var root = this, | |
| previous_async = root.async; | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = async; | |
| } | |
| else { | |
| root.async = async; | |
| } | |
| async.noConflict = function () { | |
| root.async = previous_async; | |
| return async; | |
| }; | |
| //// cross-browser compatiblity functions //// | |
| var _forEach = function (arr, iterator) { | |
| if (arr.forEach) { | |
| return arr.forEach(iterator); | |
| } | |
| for (var i = 0; i < arr.length; i += 1) { | |
| iterator(arr[i], i, arr); | |
| } | |
| }; | |
| var _map = function (arr, iterator) { | |
| if (arr.map) { | |
| return arr.map(iterator); | |
| } | |
| var results = []; | |
| _forEach(arr, function (x, i, a) { | |
| results.push(iterator(x, i, a)); | |
| }); | |
| return results; | |
| }; | |
| var _reduce = function (arr, iterator, memo) { | |
| if (arr.reduce) { | |
| return arr.reduce(iterator, memo); | |
| } | |
| _forEach(arr, function (x, i, a) { | |
| memo = iterator(memo, x, i, a); | |
| }); | |
| return memo; | |
| }; | |
| var _keys = function (obj) { | |
| if (Object.keys) { | |
| return Object.keys(obj); | |
| } | |
| var keys = []; | |
| for (var k in obj) { | |
| if (obj.hasOwnProperty(k)) { | |
| keys.push(k); | |
| } | |
| } | |
| return keys; | |
| }; | |
| //// exported async module functions //// | |
| //// nextTick implementation with browser-compatible fallback //// | |
| if (typeof process === 'undefined' || !(process.nextTick)) { | |
| async.nextTick = function (fn) { | |
| setTimeout(fn, 0); | |
| }; | |
| } | |
| else { | |
| async.nextTick = process.nextTick; | |
| } | |
| async.forEach = function (arr, iterator, callback) { | |
| callback = callback || function () {}; | |
| if (!arr.length) { | |
| return callback(); | |
| } | |
| var completed = 0; | |
| _forEach(arr, function (x) { | |
| iterator(x, function (err) { | |
| if (err) { | |
| callback(err); | |
| callback = function () {}; | |
| } | |
| else { | |
| completed += 1; | |
| if (completed === arr.length) { | |
| callback(); | |
| } | |
| } | |
| }); | |
| }); | |
| }; | |
| async.forEachSeries = function (arr, iterator, callback) { | |
| callback = callback || function () {}; | |
| if (!arr.length) { | |
| return callback(); | |
| } | |
| var completed = 0; | |
| var iterate = function () { | |
| iterator(arr[completed], function (err) { | |
| if (err) { | |
| callback(err); | |
| callback = function () {}; | |
| } | |
| else { | |
| completed += 1; | |
| if (completed === arr.length) { | |
| callback(); | |
| } | |
| else { | |
| iterate(); | |
| } | |
| } | |
| }); | |
| }; | |
| iterate(); | |
| }; | |
| async.forEachLimit = function (arr, limit, iterator, callback) { | |
| callback = callback || function () {}; | |
| if (!arr.length || limit <= 0) { | |
| return callback(); | |
| } | |
| var completed = 0; | |
| var started = 0; | |
| var running = 0; | |
| (function replenish () { | |
| if (completed === arr.length) { | |
| return callback(); | |
| } | |
| while (running < limit && started < arr.length) { | |
| iterator(arr[started], function (err) { | |
| if (err) { | |
| callback(err); | |
| callback = function () {}; | |
| } | |
| else { | |
| completed += 1; | |
| running -= 1; | |
| if (completed === arr.length) { | |
| callback(); | |
| } | |
| else { | |
| replenish(); | |
| } | |
| } | |
| }); | |
| started += 1; | |
| running += 1; | |
| } | |
| })(); | |
| }; | |
| var doParallel = function (fn) { | |
| return function () { | |
| var args = Array.prototype.slice.call(arguments); | |
| return fn.apply(null, [async.forEach].concat(args)); | |
| }; | |
| }; | |
| var doSeries = function (fn) { | |
| return function () { | |
| var args = Array.prototype.slice.call(arguments); | |
| return fn.apply(null, [async.forEachSeries].concat(args)); | |
| }; | |
| }; | |
| var _asyncMap = function (eachfn, arr, iterator, callback) { | |
| var results = []; | |
| arr = _map(arr, function (x, i) { | |
| return {index: i, value: x}; | |
| }); | |
| eachfn(arr, function (x, callback) { | |
| iterator(x.value, function (err, v) { | |
| results[x.index] = v; | |
| callback(err); | |
| }); | |
| }, function (err) { | |
| callback(err, results); | |
| }); | |
| }; | |
| async.map = doParallel(_asyncMap); | |
| async.mapSeries = doSeries(_asyncMap); | |
| // reduce only has a series version, as doing reduce in parallel won't | |
| // work in many situations. | |
| async.reduce = function (arr, memo, iterator, callback) { | |
| async.forEachSeries(arr, function (x, callback) { | |
| iterator(memo, x, function (err, v) { | |
| memo = v; | |
| callback(err); | |
| }); | |
| }, function (err) { | |
| callback(err, memo); | |
| }); | |
| }; | |
| // inject alias | |
| async.inject = async.reduce; | |
| // foldl alias | |
| async.foldl = async.reduce; | |
| async.reduceRight = function (arr, memo, iterator, callback) { | |
| var reversed = _map(arr, function (x) { | |
| return x; | |
| }).reverse(); | |
| async.reduce(reversed, memo, iterator, callback); | |
| }; | |
| // foldr alias | |
| async.foldr = async.reduceRight; | |
| var _filter = function (eachfn, arr, iterator, callback) { | |
| var results = []; | |
| arr = _map(arr, function (x, i) { | |
| return {index: i, value: x}; | |
| }); | |
| eachfn(arr, function (x, callback) { | |
| iterator(x.value, function (v) { | |
| if (v) { | |
| results.push(x); | |
| } | |
| callback(); | |
| }); | |
| }, function (err) { | |
| callback(_map(results.sort(function (a, b) { | |
| return a.index - b.index; | |
| }), function (x) { | |
| return x.value; | |
| })); | |
| }); | |
| }; | |
| async.filter = doParallel(_filter); | |
| async.filterSeries = doSeries(_filter); | |
| // select alias | |
| async.select = async.filter; | |
| async.selectSeries = async.filterSeries; | |
| var _reject = function (eachfn, arr, iterator, callback) { | |
| var results = []; | |
| arr = _map(arr, function (x, i) { | |
| return {index: i, value: x}; | |
| }); | |
| eachfn(arr, function (x, callback) { | |
| iterator(x.value, function (v) { | |
| if (!v) { | |
| results.push(x); | |
| } | |
| callback(); | |
| }); | |
| }, function (err) { | |
| callback(_map(results.sort(function (a, b) { | |
| return a.index - b.index; | |
| }), function (x) { | |
| return x.value; | |
| })); | |
| }); | |
| }; | |
| async.reject = doParallel(_reject); | |
| async.rejectSeries = doSeries(_reject); | |
| var _detect = function (eachfn, arr, iterator, main_callback) { | |
| eachfn(arr, function (x, callback) { | |
| iterator(x, function (result) { | |
| if (result) { | |
| main_callback(x); | |
| main_callback = function () {}; | |
| } | |
| else { | |
| callback(); | |
| } | |
| }); | |
| }, function (err) { | |
| main_callback(); | |
| }); | |
| }; | |
| async.detect = doParallel(_detect); | |
| async.detectSeries = doSeries(_detect); | |
| async.some = function (arr, iterator, main_callback) { | |
| async.forEach(arr, function (x, callback) { | |
| iterator(x, function (v) { | |
| if (v) { | |
| main_callback(true); | |
| main_callback = function () {}; | |
| } | |
| callback(); | |
| }); | |
| }, function (err) { | |
| main_callback(false); | |
| }); | |
| }; | |
| // any alias | |
| async.any = async.some; | |
| async.every = function (arr, iterator, main_callback) { | |
| async.forEach(arr, function (x, callback) { | |
| iterator(x, function (v) { | |
| if (!v) { | |
| main_callback(false); | |
| main_callback = function () {}; | |
| } | |
| callback(); | |
| }); | |
| }, function (err) { | |
| main_callback(true); | |
| }); | |
| }; | |
| // all alias | |
| async.all = async.every; | |
| async.sortBy = function (arr, iterator, callback) { | |
| async.map(arr, function (x, callback) { | |
| iterator(x, function (err, criteria) { | |
| if (err) { | |
| callback(err); | |
| } | |
| else { | |
| callback(null, {value: x, criteria: criteria}); | |
| } | |
| }); | |
| }, function (err, results) { | |
| if (err) { | |
| return callback(err); | |
| } | |
| else { | |
| var fn = function (left, right) { | |
| var a = left.criteria, b = right.criteria; | |
| return a < b ? -1 : a > b ? 1 : 0; | |
| }; | |
| callback(null, _map(results.sort(fn), function (x) { | |
| return x.value; | |
| })); | |
| } | |
| }); | |
| }; | |
| async.auto = function (tasks, callback) { | |
| callback = callback || function () {}; | |
| var keys = _keys(tasks); | |
| if (!keys.length) { | |
| return callback(null); | |
| } | |
| var results = {}; | |
| var listeners = []; | |
| var addListener = function (fn) { | |
| listeners.unshift(fn); | |
| }; | |
| var removeListener = function (fn) { | |
| for (var i = 0; i < listeners.length; i += 1) { | |
| if (listeners[i] === fn) { | |
| listeners.splice(i, 1); | |
| return; | |
| } | |
| } | |
| }; | |
| var taskComplete = function () { | |
| _forEach(listeners.slice(0), function (fn) { | |
| fn(); | |
| }); | |
| }; | |
| addListener(function () { | |
| if (_keys(results).length === keys.length) { | |
| callback(null, results); | |
| callback = function () {}; | |
| } | |
| }); | |
| _forEach(keys, function (k) { | |
| var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; | |
| var taskCallback = function (err) { | |
| if (err) { | |
| callback(err); | |
| // stop subsequent errors hitting callback multiple times | |
| callback = function () {}; | |
| } | |
| else { | |
| var args = Array.prototype.slice.call(arguments, 1); | |
| if (args.length <= 1) { | |
| args = args[0]; | |
| } | |
| results[k] = args; | |
| taskComplete(); | |
| } | |
| }; | |
| var requires = task.slice(0, Math.abs(task.length - 1)) || []; | |
| var ready = function () { | |
| return _reduce(requires, function (a, x) { | |
| return (a && results.hasOwnProperty(x)); | |
| }, true); | |
| }; | |
| if (ready()) { | |
| task[task.length - 1](taskCallback, results); | |
| } | |
| else { | |
| var listener = function () { | |
| if (ready()) { | |
| removeListener(listener); | |
| task[task.length - 1](taskCallback, results); | |
| } | |
| }; | |
| addListener(listener); | |
| } | |
| }); | |
| }; | |
| async.waterfall = function (tasks, callback) { | |
| callback = callback || function () {}; | |
| if (!tasks.length) { | |
| return callback(); | |
| } | |
| var wrapIterator = function (iterator) { | |
| return function (err) { | |
| if (err) { | |
| callback(err); | |
| callback = function () {}; | |
| } | |
| else { | |
| var args = Array.prototype.slice.call(arguments, 1); | |
| var next = iterator.next(); | |
| if (next) { | |
| args.push(wrapIterator(next)); | |
| } | |
| else { | |
| args.push(callback); | |
| } | |
| async.nextTick(function () { | |
| iterator.apply(null, args); | |
| }); | |
| } | |
| }; | |
| }; | |
| wrapIterator(async.iterator(tasks))(); | |
| }; | |
| async.parallel = function (tasks, callback) { | |
| callback = callback || function () {}; | |
| if (tasks.constructor === Array) { | |
| async.map(tasks, function (fn, callback) { | |
| if (fn) { | |
| fn(function (err) { | |
| var args = Array.prototype.slice.call(arguments, 1); | |
| if (args.length <= 1) { | |
| args = args[0]; | |
| } | |
| callback.call(null, err, args); | |
| }); | |
| } | |
| }, callback); | |
| } | |
| else { | |
| var results = {}; | |
| async.forEach(_keys(tasks), function (k, callback) { | |
| tasks[k](function (err) { | |
| var args = Array.prototype.slice.call(arguments, 1); | |
| if (args.length <= 1) { | |
| args = args[0]; | |
| } | |
| results[k] = args; | |
| callback(err); | |
| }); | |
| }, function (err) { | |
| callback(err, results); | |
| }); | |
| } | |
| }; | |
| async.series = function (tasks, callback) { | |
| callback = callback || function () {}; | |
| if (tasks.constructor === Array) { | |
| async.mapSeries(tasks, function (fn, callback) { | |
| if (fn) { | |
| fn(function (err) { | |
| var args = Array.prototype.slice.call(arguments, 1); | |
| if (args.length <= 1) { | |
| args = args[0]; | |
| } | |
| callback.call(null, err, args); | |
| }); | |
| } | |
| }, callback); | |
| } | |
| else { | |
| var results = {}; | |
| async.forEachSeries(_keys(tasks), function (k, callback) { | |
| tasks[k](function (err) { | |
| var args = Array.prototype.slice.call(arguments, 1); | |
| if (args.length <= 1) { | |
| args = args[0]; | |
| } | |
| results[k] = args; | |
| callback(err); | |
| }); | |
| }, function (err) { | |
| callback(err, results); | |
| }); | |
| } | |
| }; | |
| async.iterator = function (tasks) { | |
| var makeCallback = function (index) { | |
| var fn = function () { | |
| if (tasks.length) { | |
| tasks[index].apply(null, arguments); | |
| } | |
| return fn.next(); | |
| }; | |
| fn.next = function () { | |
| return (index < tasks.length - 1) ? makeCallback(index + 1): null; | |
| }; | |
| return fn; | |
| }; | |
| return makeCallback(0); | |
| }; | |
| async.apply = function (fn) { | |
| var args = Array.prototype.slice.call(arguments, 1); | |
| return function () { | |
| return fn.apply( | |
| null, args.concat(Array.prototype.slice.call(arguments)) | |
| ); | |
| }; | |
| }; | |
| var _concat = function (eachfn, arr, fn, callback) { | |
| var r = []; | |
| eachfn(arr, function (x, cb) { | |
| fn(x, function (err, y) { | |
| r = r.concat(y || []); | |
| cb(err); | |
| }); | |
| }, function (err) { | |
| callback(err, r); | |
| }); | |
| }; | |
| async.concat = doParallel(_concat); | |
| async.concatSeries = doSeries(_concat); | |
| async.whilst = function (test, iterator, callback) { | |
| if (test()) { | |
| iterator(function (err) { | |
| if (err) { | |
| return callback(err); | |
| } | |
| async.whilst(test, iterator, callback); | |
| }); | |
| } | |
| else { | |
| callback(); | |
| } | |
| }; | |
| async.until = function (test, iterator, callback) { | |
| if (!test()) { | |
| iterator(function (err) { | |
| if (err) { | |
| return callback(err); | |
| } | |
| async.until(test, iterator, callback); | |
| }); | |
| } | |
| else { | |
| callback(); | |
| } | |
| }; | |
| async.queue = function (worker, concurrency) { | |
| var workers = 0; | |
| var q = { | |
| tasks: [], | |
| concurrency: concurrency, | |
| saturated: null, | |
| empty: null, | |
| drain: null, | |
| push: function (data, callback) { | |
| if(data.constructor !== Array) { | |
| data = [data]; | |
| } | |
| _forEach(data, function(task) { | |
| q.tasks.push({ | |
| data: task, | |
| callback: typeof callback === 'function' ? callback : null | |
| }); | |
| if (q.saturated && q.tasks.length == concurrency) { | |
| q.saturated(); | |
| } | |
| async.nextTick(q.process); | |
| }); | |
| }, | |
| process: function () { | |
| if (workers < q.concurrency && q.tasks.length) { | |
| var task = q.tasks.shift(); | |
| if(q.empty && q.tasks.length == 0) q.empty(); | |
| workers += 1; | |
| worker(task.data, function () { | |
| workers -= 1; | |
| if (task.callback) { | |
| task.callback.apply(task, arguments); | |
| } | |
| if(q.drain && q.tasks.length + workers == 0) q.drain(); | |
| q.process(); | |
| }); | |
| } | |
| }, | |
| length: function () { | |
| return q.tasks.length; | |
| }, | |
| running: function () { | |
| return workers; | |
| }, | |
| rateLimit: function (timeout) { | |
| // mixin the ratelimiter | |
| rateLimiter.call(q); | |
| // and invoke it | |
| q.rateLimit(timeout); | |
| // return original queue for chaining | |
| return q; | |
| } | |
| }; | |
| return q; | |
| }; | |
| var _console_fn = function (name) { | |
| return function (fn) { | |
| var args = Array.prototype.slice.call(arguments, 1); | |
| fn.apply(null, args.concat([function (err) { | |
| var args = Array.prototype.slice.call(arguments, 1); | |
| if (typeof console !== 'undefined') { | |
| if (err) { | |
| if (console.error) { | |
| console.error(err); | |
| } | |
| } | |
| else if (console[name]) { | |
| _forEach(args, function (x) { | |
| console[name](x); | |
| }); | |
| } | |
| } | |
| }])); | |
| }; | |
| }; | |
| async.log = _console_fn('log'); | |
| async.dir = _console_fn('dir'); | |
| /*async.info = _console_fn('info'); | |
| async.warn = _console_fn('warn'); | |
| async.error = _console_fn('error');*/ | |
| async.memoize = function (fn, hasher) { | |
| var memo = {}; | |
| var queues = {}; | |
| hasher = hasher || function (x) { | |
| return x; | |
| }; | |
| var memoized = function () { | |
| var args = Array.prototype.slice.call(arguments); | |
| var callback = args.pop(); | |
| var key = hasher.apply(null, args); | |
| if (key in memo) { | |
| callback.apply(null, memo[key]); | |
| } | |
| else if (key in queues) { | |
| queues[key].push(callback); | |
| } | |
| else { | |
| queues[key] = [callback]; | |
| fn.apply(null, args.concat([function () { | |
| memo[key] = arguments; | |
| var q = queues[key]; | |
| delete queues[key]; | |
| for (var i = 0, l = q.length; i < l; i++) { | |
| q[i].apply(null, arguments); | |
| } | |
| }])); | |
| } | |
| }; | |
| memoized.unmemoized = fn; | |
| return memoized; | |
| }; | |
| async.unmemoize = function (fn) { | |
| return function () { | |
| return (fn.unmemoized || fn).apply(null, arguments); | |
| } | |
| }; | |
| /** | |
| * Add rate limiting capabilities to a queue. | |
| * The timeout should indicate the minimum number of milliseconds before | |
| * invoking the next item in the queue. | |
| * So for a 100/min. rate limit, use (60000 / 100) | |
| * | |
| * Invoke like | |
| * rateLimiter.call(queue); | |
| * queue.rateLimit(timeout); | |
| */ | |
| var rateLimiter = function () { | |
| var queue = this; | |
| // keep the original methods, so we can invoke them later on | |
| var _push = queue.push; | |
| var _drain = queue.drain; | |
| // copy the original tasks | |
| var internalQueue = queue.tasks.splice(0); | |
| queue.tasks = []; | |
| // number of items that need to finish | |
| var toProcess = internalQueue.length; | |
| // internal state | |
| this.$rateLimitTimeout = 0; | |
| this.$rateLimitBusy = false; | |
| this.$rateLimitInterval = null; | |
| // wrapper around push that pushes to our own queue instead of tasks | |
| this.push = function (data, callback) { | |
| if(data.constructor !== Array) { | |
| data = [data]; | |
| } | |
| data.forEach(function (task) { | |
| internalQueue.push({ data: task, callback: callback }); | |
| }); | |
| toProcess += data.length; | |
| }; | |
| // kick off the rate limiter | |
| this.rateLimit = function (timeout) { | |
| this.$rateLimitTimeout = timeout || 1000; | |
| this.$processNextRateLimited(); | |
| }; | |
| // invoke the rate limit implementation with a timeout | |
| this.$processNextRateLimited = function () { | |
| var self = this; | |
| self.$rateLimitInterval = setInterval(function () { | |
| self.$processNextRateLimitedImpl(); | |
| }, self.$rateLimitTimeout); | |
| }; | |
| // process the next item in our rate limited queue | |
| this.$processNextRateLimitedImpl = function () { | |
| var self = this; | |
| // grab next item... | |
| // disadvantage of this approach (doing it in the impl) is that the event loop will die | |
| // 1 timeout later than required. Might need to fix that one day. | |
| var item = internalQueue.shift(0); | |
| if (!item) { | |
| return; | |
| } | |
| // we're busy | |
| self.$rateLimitBusy = true; | |
| // the 'drain' function can be overwritten at this point, | |
| // as we don't have control over when it happens... | |
| if (self.drain !== $emptyDrain) { | |
| _drain = self.drain; | |
| self.drain = $emptyDrain; | |
| } | |
| // invoke original push function of the queue | |
| _push(item.data, function () { | |
| if (typeof item.callback === "function") { | |
| item.callback.apply(this, arguments); | |
| } | |
| toProcess -= 1; | |
| if (toProcess === 0) { | |
| self.$rateLimitBusy = false; | |
| clearInterval(self.$rateLimitInterval); | |
| if (typeof _drain === "function") { | |
| _drain(); | |
| } | |
| } | |
| }); | |
| }; | |
| var $emptyDrain = function () {}; | |
| // the original drain function of a queue needs to be overwritten | |
| // otherwise the user will be flooded with messages | |
| this.drain = $emptyDrain; | |
| }; | |
| }()); |