| 'use strict'; |
| |
| Object.defineProperty(exports, "__esModule", { |
| value: true |
| }); |
| |
| exports.default = function (tasks, concurrency, callback) { |
| if (typeof concurrency === 'function') { |
| // concurrency is optional, shift the args. |
| callback = concurrency; |
| concurrency = null; |
| } |
| callback = (0, _once2.default)(callback || _noop2.default); |
| var keys = (0, _keys2.default)(tasks); |
| var numTasks = keys.length; |
| if (!numTasks) { |
| return callback(null); |
| } |
| if (!concurrency) { |
| concurrency = numTasks; |
| } |
| |
| var results = {}; |
| var runningTasks = 0; |
| 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 = {}; |
| |
| (0, _baseForOwn2.default)(tasks, function (task, key) { |
| if (!(0, _isArray2.default)(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; |
| |
| (0, _arrayEach2.default)(dependencies, function (dependencyName) { |
| if (!tasks[dependencyName]) { |
| throw new Error('async.auto task `' + key + '` has a non-existent dependency `' + dependencyName + '` in ' + dependencies.join(', ')); |
| } |
| addListener(dependencyName, function () { |
| remainingDependencies--; |
| if (remainingDependencies === 0) { |
| enqueueTask(key, task); |
| } |
| }); |
| }); |
| }); |
| |
| checkForDeadlocks(); |
| processQueue(); |
| |
| function enqueueTask(key, task) { |
| readyTasks.push(function () { |
| runTask(key, task); |
| }); |
| } |
| |
| function processQueue() { |
| 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] || []; |
| (0, _arrayEach2.default)(taskListeners, function (fn) { |
| fn(); |
| }); |
| processQueue(); |
| } |
| |
| function runTask(key, task) { |
| if (hasError) return; |
| |
| var taskCallback = (0, _onlyOnce2.default)(function (err, result) { |
| runningTasks--; |
| if (arguments.length > 2) { |
| result = (0, _slice2.default)(arguments, 1); |
| } |
| if (err) { |
| var safeResults = {}; |
| (0, _baseForOwn2.default)(results, function (val, rkey) { |
| safeResults[rkey] = val; |
| }); |
| safeResults[key] = result; |
| hasError = true; |
| listeners = Object.create(null); |
| |
| callback(err, safeResults); |
| } else { |
| results[key] = result; |
| taskComplete(key); |
| } |
| }); |
| |
| runningTasks++; |
| var taskFn = (0, _wrapAsync2.default)(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++; |
| (0, _arrayEach2.default)(getDependents(currentTask), function (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 = []; |
| (0, _baseForOwn2.default)(tasks, function (task, key) { |
| if ((0, _isArray2.default)(task) && (0, _baseIndexOf2.default)(task, taskName, 0) >= 0) { |
| result.push(key); |
| } |
| }); |
| return result; |
| } |
| }; |
| |
| var _arrayEach = require('lodash/_arrayEach'); |
| |
| var _arrayEach2 = _interopRequireDefault(_arrayEach); |
| |
| var _baseForOwn = require('lodash/_baseForOwn'); |
| |
| var _baseForOwn2 = _interopRequireDefault(_baseForOwn); |
| |
| var _baseIndexOf = require('lodash/_baseIndexOf'); |
| |
| var _baseIndexOf2 = _interopRequireDefault(_baseIndexOf); |
| |
| var _isArray = require('lodash/isArray'); |
| |
| var _isArray2 = _interopRequireDefault(_isArray); |
| |
| var _keys = require('lodash/keys'); |
| |
| var _keys2 = _interopRequireDefault(_keys); |
| |
| var _noop = require('lodash/noop'); |
| |
| var _noop2 = _interopRequireDefault(_noop); |
| |
| var _slice = require('./internal/slice'); |
| |
| var _slice2 = _interopRequireDefault(_slice); |
| |
| var _once = require('./internal/once'); |
| |
| var _once2 = _interopRequireDefault(_once); |
| |
| var _onlyOnce = require('./internal/onlyOnce'); |
| |
| var _onlyOnce2 = _interopRequireDefault(_onlyOnce); |
| |
| var _wrapAsync = require('./internal/wrapAsync'); |
| |
| var _wrapAsync2 = _interopRequireDefault(_wrapAsync); |
| |
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } |
| |
| module.exports = exports['default']; |
| |
| /** |
| * 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 undefined |
| * @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); |
| * }); |
| */ |