auto.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = function (tasks, concurrency, callback) {
  6. if (typeof concurrency === 'function') {
  7. // concurrency is optional, shift the args.
  8. callback = concurrency;
  9. concurrency = null;
  10. }
  11. callback = (0, _once2.default)(callback || _noop2.default);
  12. var keys = (0, _keys2.default)(tasks);
  13. var numTasks = keys.length;
  14. if (!numTasks) {
  15. return callback(null);
  16. }
  17. if (!concurrency) {
  18. concurrency = numTasks;
  19. }
  20. var results = {};
  21. var runningTasks = 0;
  22. var hasError = false;
  23. var listeners = Object.create(null);
  24. var readyTasks = [];
  25. // for cycle detection:
  26. var readyToCheck = []; // tasks that have been identified as reachable
  27. // without the possibility of returning to an ancestor task
  28. var uncheckedDependencies = {};
  29. (0, _baseForOwn2.default)(tasks, function (task, key) {
  30. if (!(0, _isArray2.default)(task)) {
  31. // no dependencies
  32. enqueueTask(key, [task]);
  33. readyToCheck.push(key);
  34. return;
  35. }
  36. var dependencies = task.slice(0, task.length - 1);
  37. var remainingDependencies = dependencies.length;
  38. if (remainingDependencies === 0) {
  39. enqueueTask(key, task);
  40. readyToCheck.push(key);
  41. return;
  42. }
  43. uncheckedDependencies[key] = remainingDependencies;
  44. (0, _arrayEach2.default)(dependencies, function (dependencyName) {
  45. if (!tasks[dependencyName]) {
  46. throw new Error('async.auto task `' + key + '` has a non-existent dependency `' + dependencyName + '` in ' + dependencies.join(', '));
  47. }
  48. addListener(dependencyName, function () {
  49. remainingDependencies--;
  50. if (remainingDependencies === 0) {
  51. enqueueTask(key, task);
  52. }
  53. });
  54. });
  55. });
  56. checkForDeadlocks();
  57. processQueue();
  58. function enqueueTask(key, task) {
  59. readyTasks.push(function () {
  60. runTask(key, task);
  61. });
  62. }
  63. function processQueue() {
  64. if (readyTasks.length === 0 && runningTasks === 0) {
  65. return callback(null, results);
  66. }
  67. while (readyTasks.length && runningTasks < concurrency) {
  68. var run = readyTasks.shift();
  69. run();
  70. }
  71. }
  72. function addListener(taskName, fn) {
  73. var taskListeners = listeners[taskName];
  74. if (!taskListeners) {
  75. taskListeners = listeners[taskName] = [];
  76. }
  77. taskListeners.push(fn);
  78. }
  79. function taskComplete(taskName) {
  80. var taskListeners = listeners[taskName] || [];
  81. (0, _arrayEach2.default)(taskListeners, function (fn) {
  82. fn();
  83. });
  84. processQueue();
  85. }
  86. function runTask(key, task) {
  87. if (hasError) return;
  88. var taskCallback = (0, _onlyOnce2.default)(function (err, result) {
  89. runningTasks--;
  90. if (arguments.length > 2) {
  91. result = (0, _slice2.default)(arguments, 1);
  92. }
  93. if (err) {
  94. var safeResults = {};
  95. (0, _baseForOwn2.default)(results, function (val, rkey) {
  96. safeResults[rkey] = val;
  97. });
  98. safeResults[key] = result;
  99. hasError = true;
  100. listeners = Object.create(null);
  101. callback(err, safeResults);
  102. } else {
  103. results[key] = result;
  104. taskComplete(key);
  105. }
  106. });
  107. runningTasks++;
  108. var taskFn = (0, _wrapAsync2.default)(task[task.length - 1]);
  109. if (task.length > 1) {
  110. taskFn(results, taskCallback);
  111. } else {
  112. taskFn(taskCallback);
  113. }
  114. }
  115. function checkForDeadlocks() {
  116. // Kahn's algorithm
  117. // https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
  118. // http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html
  119. var currentTask;
  120. var counter = 0;
  121. while (readyToCheck.length) {
  122. currentTask = readyToCheck.pop();
  123. counter++;
  124. (0, _arrayEach2.default)(getDependents(currentTask), function (dependent) {
  125. if (--uncheckedDependencies[dependent] === 0) {
  126. readyToCheck.push(dependent);
  127. }
  128. });
  129. }
  130. if (counter !== numTasks) {
  131. throw new Error('async.auto cannot execute tasks due to a recursive dependency');
  132. }
  133. }
  134. function getDependents(taskName) {
  135. var result = [];
  136. (0, _baseForOwn2.default)(tasks, function (task, key) {
  137. if ((0, _isArray2.default)(task) && (0, _baseIndexOf2.default)(task, taskName, 0) >= 0) {
  138. result.push(key);
  139. }
  140. });
  141. return result;
  142. }
  143. };
  144. var _arrayEach = require('lodash/_arrayEach');
  145. var _arrayEach2 = _interopRequireDefault(_arrayEach);
  146. var _baseForOwn = require('lodash/_baseForOwn');
  147. var _baseForOwn2 = _interopRequireDefault(_baseForOwn);
  148. var _baseIndexOf = require('lodash/_baseIndexOf');
  149. var _baseIndexOf2 = _interopRequireDefault(_baseIndexOf);
  150. var _isArray = require('lodash/isArray');
  151. var _isArray2 = _interopRequireDefault(_isArray);
  152. var _keys = require('lodash/keys');
  153. var _keys2 = _interopRequireDefault(_keys);
  154. var _noop = require('lodash/noop');
  155. var _noop2 = _interopRequireDefault(_noop);
  156. var _slice = require('./internal/slice');
  157. var _slice2 = _interopRequireDefault(_slice);
  158. var _once = require('./internal/once');
  159. var _once2 = _interopRequireDefault(_once);
  160. var _onlyOnce = require('./internal/onlyOnce');
  161. var _onlyOnce2 = _interopRequireDefault(_onlyOnce);
  162. var _wrapAsync = require('./internal/wrapAsync');
  163. var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
  164. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  165. module.exports = exports['default'];
  166. /**
  167. * Determines the best order for running the {@link AsyncFunction}s in `tasks`, based on
  168. * their requirements. Each function can optionally depend on other functions
  169. * being completed first, and each function is run as soon as its requirements
  170. * are satisfied.
  171. *
  172. * If any of the {@link AsyncFunction}s pass an error to their callback, the `auto` sequence
  173. * will stop. Further tasks will not execute (so any other functions depending
  174. * on it will not run), and the main `callback` is immediately called with the
  175. * error.
  176. *
  177. * {@link AsyncFunction}s also receive an object containing the results of functions which
  178. * have completed so far as the first argument, if they have dependencies. If a
  179. * task function has no dependencies, it will only be passed a callback.
  180. *
  181. * @name auto
  182. * @static
  183. * @memberOf module:ControlFlow
  184. * @method
  185. * @category Control Flow
  186. * @param {Object} tasks - An object. Each of its properties is either a
  187. * function or an array of requirements, with the {@link AsyncFunction} itself the last item
  188. * in the array. The object's key of a property serves as the name of the task
  189. * defined by that property, i.e. can be used when specifying requirements for
  190. * other tasks. The function receives one or two arguments:
  191. * * a `results` object, containing the results of the previously executed
  192. * functions, only passed if the task has any dependencies,
  193. * * a `callback(err, result)` function, which must be called when finished,
  194. * passing an `error` (which can be `null`) and the result of the function's
  195. * execution.
  196. * @param {number} [concurrency=Infinity] - An optional `integer` for
  197. * determining the maximum number of tasks that can be run in parallel. By
  198. * default, as many as possible.
  199. * @param {Function} [callback] - An optional callback which is called when all
  200. * the tasks have been completed. It receives the `err` argument if any `tasks`
  201. * pass an error to their callback. Results are always returned; however, if an
  202. * error occurs, no further `tasks` will be performed, and the results object
  203. * will only contain partial results. Invoked with (err, results).
  204. * @returns undefined
  205. * @example
  206. *
  207. * async.auto({
  208. * // this function will just be passed a callback
  209. * readData: async.apply(fs.readFile, 'data.txt', 'utf-8'),
  210. * showData: ['readData', function(results, cb) {
  211. * // results.readData is the file's contents
  212. * // ...
  213. * }]
  214. * }, callback);
  215. *
  216. * async.auto({
  217. * get_data: function(callback) {
  218. * console.log('in get_data');
  219. * // async code to get some data
  220. * callback(null, 'data', 'converted to array');
  221. * },
  222. * make_folder: function(callback) {
  223. * console.log('in make_folder');
  224. * // async code to create a directory to store a file in
  225. * // this is run at the same time as getting the data
  226. * callback(null, 'folder');
  227. * },
  228. * write_file: ['get_data', 'make_folder', function(results, callback) {
  229. * console.log('in write_file', JSON.stringify(results));
  230. * // once there is some data and the directory exists,
  231. * // write the data to a file in the directory
  232. * callback(null, 'filename');
  233. * }],
  234. * email_link: ['write_file', function(results, callback) {
  235. * console.log('in email_link', JSON.stringify(results));
  236. * // once the file is written let's email a link to it...
  237. * // results.write_file contains the filename returned by write_file.
  238. * callback(null, {'file':results.write_file, 'email':'user@example.com'});
  239. * }]
  240. * }, function(err, results) {
  241. * console.log('err = ', err);
  242. * console.log('results = ', results);
  243. * });
  244. */