auto.js 12 KB

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