UNPKG

recoder-code

Version:

🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!

1,358 lines (1,264 loc) • 225 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.async = {})); })(this, (function (exports) { 'use strict'; /** * Creates a continuation function with some arguments already applied. * * Useful as a shorthand when combined with other control flow functions. Any * arguments passed to the returned function are added to the arguments * originally passed to apply. * * @name apply * @static * @memberOf module:Utils * @method * @category Util * @param {Function} fn - The function you want to eventually apply all * arguments to. Invokes with (arguments...). * @param {...*} arguments... - Any number of arguments to automatically apply * when the continuation is called. * @returns {Function} the partially-applied function * @example * * // using apply * async.parallel([ * async.apply(fs.writeFile, 'testfile1', 'test1'), * async.apply(fs.writeFile, 'testfile2', 'test2') * ]); * * * // the same process without using apply * async.parallel([ * function(callback) { * fs.writeFile('testfile1', 'test1', callback); * }, * function(callback) { * fs.writeFile('testfile2', 'test2', callback); * } * ]); * * // It's possible to pass any number of additional arguments when calling the * // continuation: * * node> var fn = async.apply(sys.puts, 'one'); * node> fn('two', 'three'); * one * two * three */ function apply(fn, ...args) { return (...callArgs) => fn(...args,...callArgs); } function initialParams (fn) { return function (...args/*, callback*/) { var callback = args.pop(); return fn.call(this, args, callback); }; } /* istanbul ignore file */ var hasQueueMicrotask = typeof queueMicrotask === 'function' && queueMicrotask; var hasSetImmediate = typeof setImmediate === 'function' && setImmediate; var hasNextTick = typeof process === 'object' && typeof process.nextTick === 'function'; function fallback(fn) { setTimeout(fn, 0); } function wrap(defer) { return (fn, ...args) => defer(() => fn(...args)); } var _defer$1; if (hasQueueMicrotask) { _defer$1 = queueMicrotask; } else if (hasSetImmediate) { _defer$1 = setImmediate; } else if (hasNextTick) { _defer$1 = process.nextTick; } else { _defer$1 = fallback; } var setImmediate$1 = wrap(_defer$1); /** * Take a sync function and make it async, passing its return value to a * callback. This is useful for plugging sync functions into a waterfall, * series, or other async functions. Any arguments passed to the generated * function will be passed to the wrapped function (except for the final * callback argument). Errors thrown will be passed to the callback. * * If the function passed to `asyncify` returns a Promise, that promises's * resolved/rejected state will be used to call the callback, rather than simply * the synchronous return value. * * This also means you can asyncify ES2017 `async` functions. * * @name asyncify * @static * @memberOf module:Utils * @method * @alias wrapSync * @category Util * @param {Function} func - The synchronous function, or Promise-returning * function to convert to an {@link AsyncFunction}. * @returns {AsyncFunction} An asynchronous wrapper of the `func`. To be * invoked with `(args..., callback)`. * @example * * // passing a regular synchronous function * async.waterfall([ * async.apply(fs.readFile, filename, "utf8"), * async.asyncify(JSON.parse), * function (data, next) { * // data is the result of parsing the text. * // If there was a parsing error, it would have been caught. * } * ], callback); * * // passing a function returning a promise * async.waterfall([ * async.apply(fs.readFile, filename, "utf8"), * async.asyncify(function (contents) { * return db.model.create(contents); * }), * function (model, next) { * // `model` is the instantiated model object. * // If there was an error, this function would be skipped. * } * ], callback); * * // es2017 example, though `asyncify` is not needed if your JS environment * // supports async functions out of the box * var q = async.queue(async.asyncify(async function(file) { * var intermediateStep = await processFile(file); * return await somePromise(intermediateStep) * })); * * q.push(files); */ function asyncify(func) { if (isAsync(func)) { return function (...args/*, callback*/) { const callback = args.pop(); const promise = func.apply(this, args); return handlePromise(promise, callback) } } return initialParams(function (args, callback) { var result; try { result = func.apply(this, args); } catch (e) { return callback(e); } // if result is Promise object if (result && typeof result.then === 'function') { return handlePromise(result, callback) } else { callback(null, result); } }); } function handlePromise(promise, callback) { return promise.then(value => { invokeCallback(callback, null, value); }, err => { invokeCallback(callback, err && (err instanceof Error || err.message) ? err : new Error(err)); }); } function invokeCallback(callback, error, value) { try { callback(error, value); } catch (err) { setImmediate$1(e => { throw e }, err); } } function isAsync(fn) { return fn[Symbol.toStringTag] === 'AsyncFunction'; } function isAsyncGenerator(fn) { return fn[Symbol.toStringTag] === 'AsyncGenerator'; } function isAsyncIterable(obj) { return typeof obj[Symbol.asyncIterator] === 'function'; } function wrapAsync(asyncFn) { if (typeof asyncFn !== 'function') throw new Error('expected a function') return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn; } // conditionally promisify a function. // only return a promise if a callback is omitted function awaitify (asyncFn, arity) { if (!arity) arity = asyncFn.length; if (!arity) throw new Error('arity is undefined') function awaitable (...args) { if (typeof args[arity - 1] === 'function') { return asyncFn.apply(this, args) } return new Promise((resolve, reject) => { args[arity - 1] = (err, ...cbArgs) => { if (err) return reject(err) resolve(cbArgs.length > 1 ? cbArgs : cbArgs[0]); }; asyncFn.apply(this, args); }) } return awaitable } function applyEach$1 (eachfn) { return function applyEach(fns, ...callArgs) { const go = awaitify(function (callback) { var that = this; return eachfn(fns, (fn, cb) => { wrapAsync(fn).apply(that, callArgs.concat(cb)); }, callback); }); return go; }; } function _asyncMap(eachfn, arr, iteratee, callback) { arr = arr || []; var results = []; var counter = 0; var _iteratee = wrapAsync(iteratee); return eachfn(arr, (value, _, iterCb) => { var index = counter++; _iteratee(value, (err, v) => { results[index] = v; iterCb(err); }); }, err => { callback(err, results); }); } function isArrayLike(value) { return value && typeof value.length === 'number' && value.length >= 0 && value.length % 1 === 0; } // A temporary value used to identify if the loop should be broken. // See #1064, #1293 const breakLoop = {}; function once(fn) { function wrapper (...args) { if (fn === null) return; var callFn = fn; fn = null; callFn.apply(this, args); } Object.assign(wrapper, fn); return wrapper } function getIterator (coll) { return coll[Symbol.iterator] && coll[Symbol.iterator](); } function createArrayIterator(coll) { var i = -1; var len = coll.length; return function next() { return ++i < len ? {value: coll[i], key: i} : null; } } function createES2015Iterator(iterator) { var i = -1; return function next() { var item = iterator.next(); if (item.done) return null; i++; return {value: item.value, key: i}; } } function createObjectIterator(obj) { var okeys = obj ? Object.keys(obj) : []; var i = -1; var len = okeys.length; return function next() { var key = okeys[++i]; if (key === '__proto__') { return next(); } return i < len ? {value: obj[key], key} : null; }; } function createIterator(coll) { if (isArrayLike(coll)) { return createArrayIterator(coll); } var iterator = getIterator(coll); return iterator ? createES2015Iterator(iterator) : createObjectIterator(coll); } function onlyOnce(fn) { return function (...args) { if (fn === null) throw new Error("Callback was already called."); var callFn = fn; fn = null; callFn.apply(this, args); }; } // for async generators function asyncEachOfLimit(generator, limit, iteratee, callback) { let done = false; let canceled = false; let awaiting = false; let running = 0; let idx = 0; function replenish() { //console.log('replenish') if (running >= limit || awaiting || done) return //console.log('replenish awaiting') awaiting = true; generator.next().then(({value, done: iterDone}) => { //console.log('got value', value) if (canceled || done) return awaiting = false; if (iterDone) { done = true; if (running <= 0) { //console.log('done nextCb') callback(null); } return; } running++; iteratee(value, idx, iterateeCallback); idx++; replenish(); }).catch(handleError); } function iterateeCallback(err, result) { //console.log('iterateeCallback') running -= 1; if (canceled) return if (err) return handleError(err) if (err === false) { done = true; canceled = true; return } if (result === breakLoop || (done && running <= 0)) { done = true; //console.log('done iterCb') return callback(null); } replenish(); } function handleError(err) { if (canceled) return awaiting = false; done = true; callback(err); } replenish(); } var eachOfLimit$2 = (limit) => { return (obj, iteratee, callback) => { callback = once(callback); if (limit <= 0) { throw new RangeError('concurrency limit cannot be less than 1') } if (!obj) { return callback(null); } if (isAsyncGenerator(obj)) { return asyncEachOfLimit(obj, limit, iteratee, callback) } if (isAsyncIterable(obj)) { return asyncEachOfLimit(obj[Symbol.asyncIterator](), limit, iteratee, callback) } var nextElem = createIterator(obj); var done = false; var canceled = false; var running = 0; var looping = false; function iterateeCallback(err, value) { if (canceled) return running -= 1; if (err) { done = true; callback(err); } else if (err === false) { done = true; canceled = true; } else if (value === breakLoop || (done && running <= 0)) { done = true; return callback(null); } else if (!looping) { replenish(); } } function replenish () { looping = true; while (running < limit && !done) { var elem = nextElem(); if (elem === null) { done = true; if (running <= 0) { callback(null); } return; } running += 1; iteratee(elem.value, elem.key, onlyOnce(iterateeCallback)); } looping = false; } replenish(); }; }; /** * The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a * time. * * @name eachOfLimit * @static * @memberOf module:Collections * @method * @see [async.eachOf]{@link module:Collections.eachOf} * @alias forEachOfLimit * @category Collection * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over. * @param {number} limit - The maximum number of async operations at a time. * @param {AsyncFunction} iteratee - An async function to apply to each * item in `coll`. The `key` is the item's key, or index in the case of an * array. * Invoked with (item, key, callback). * @param {Function} [callback] - A callback which is called when all * `iteratee` functions have finished, or an error occurs. Invoked with (err). * @returns {Promise} a promise, if a callback is omitted */ function eachOfLimit(coll, limit, iteratee, callback) { return eachOfLimit$2(limit)(coll, wrapAsync(iteratee), callback); } var eachOfLimit$1 = awaitify(eachOfLimit, 4); // eachOf implementation optimized for array-likes function eachOfArrayLike(coll, iteratee, callback) { callback = once(callback); var index = 0, completed = 0, {length} = coll, canceled = false; if (length === 0) { callback(null); } function iteratorCallback(err, value) { if (err === false) { canceled = true; } if (canceled === true) return if (err) { callback(err); } else if ((++completed === length) || value === breakLoop) { callback(null); } } for (; index < length; index++) { iteratee(coll[index], index, onlyOnce(iteratorCallback)); } } // a generic version of eachOf which can handle array, object, and iterator cases. function eachOfGeneric (coll, iteratee, callback) { return eachOfLimit$1(coll, Infinity, iteratee, callback); } /** * Like [`each`]{@link module:Collections.each}, except that it passes the key (or index) as the second argument * to the iteratee. * * @name eachOf * @static * @memberOf module:Collections * @method * @alias forEachOf * @category Collection * @see [async.each]{@link module:Collections.each} * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over. * @param {AsyncFunction} iteratee - A function to apply to each * item in `coll`. * The `key` is the item's key, or index in the case of an array. * Invoked with (item, key, callback). * @param {Function} [callback] - A callback which is called when all * `iteratee` functions have finished, or an error occurs. Invoked with (err). * @returns {Promise} a promise, if a callback is omitted * @example * * // dev.json is a file containing a valid json object config for dev environment * // dev.json is a file containing a valid json object config for test environment * // prod.json is a file containing a valid json object config for prod environment * // invalid.json is a file with a malformed json object * * let configs = {}; //global variable * let validConfigFileMap = {dev: 'dev.json', test: 'test.json', prod: 'prod.json'}; * let invalidConfigFileMap = {dev: 'dev.json', test: 'test.json', invalid: 'invalid.json'}; * * // asynchronous function that reads a json file and parses the contents as json object * function parseFile(file, key, callback) { * fs.readFile(file, "utf8", function(err, data) { * if (err) return calback(err); * try { * configs[key] = JSON.parse(data); * } catch (e) { * return callback(e); * } * callback(); * }); * } * * // Using callbacks * async.forEachOf(validConfigFileMap, parseFile, function (err) { * if (err) { * console.error(err); * } else { * console.log(configs); * // configs is now a map of JSON data, e.g. * // { dev: //parsed dev.json, test: //parsed test.json, prod: //parsed prod.json} * } * }); * * //Error handing * async.forEachOf(invalidConfigFileMap, parseFile, function (err) { * if (err) { * console.error(err); * // JSON parse error exception * } else { * console.log(configs); * } * }); * * // Using Promises * async.forEachOf(validConfigFileMap, parseFile) * .then( () => { * console.log(configs); * // configs is now a map of JSON data, e.g. * // { dev: //parsed dev.json, test: //parsed test.json, prod: //parsed prod.json} * }).catch( err => { * console.error(err); * }); * * //Error handing * async.forEachOf(invalidConfigFileMap, parseFile) * .then( () => { * console.log(configs); * }).catch( err => { * console.error(err); * // JSON parse error exception * }); * * // Using async/await * async () => { * try { * let result = await async.forEachOf(validConfigFileMap, parseFile); * console.log(configs); * // configs is now a map of JSON data, e.g. * // { dev: //parsed dev.json, test: //parsed test.json, prod: //parsed prod.json} * } * catch (err) { * console.log(err); * } * } * * //Error handing * async () => { * try { * let result = await async.forEachOf(invalidConfigFileMap, parseFile); * console.log(configs); * } * catch (err) { * console.log(err); * // JSON parse error exception * } * } * */ function eachOf(coll, iteratee, callback) { var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric; return eachOfImplementation(coll, wrapAsync(iteratee), callback); } var eachOf$1 = awaitify(eachOf, 3); /** * Produces a new collection of values by mapping each value in `coll` through * the `iteratee` function. The `iteratee` is called with an item from `coll` * and a callback for when it has finished processing. Each of these callbacks * takes 2 arguments: an `error`, and the transformed item from `coll`. If * `iteratee` passes an error to its callback, the main `callback` (for the * `map` function) is immediately called with the error. * * Note, that since this function applies the `iteratee` to each item in * parallel, there is no guarantee that the `iteratee` functions will complete * in order. However, the results array will be in the same order as the * original `coll`. * * If `map` is passed an Object, the results will be an Array. The results * will roughly be in the order of the original Objects' keys (but this can * vary across JavaScript engines). * * @name map * @static * @memberOf module:Collections * @method * @category Collection * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over. * @param {AsyncFunction} iteratee - An async function to apply to each item in * `coll`. * The iteratee should complete with the transformed item. * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. Results is an Array of the * transformed items from the `coll`. Invoked with (err, results). * @returns {Promise} a promise, if no callback is passed * @example * * // file1.txt is a file that is 1000 bytes in size * // file2.txt is a file that is 2000 bytes in size * // file3.txt is a file that is 3000 bytes in size * // file4.txt does not exist * * const fileList = ['file1.txt','file2.txt','file3.txt']; * const withMissingFileList = ['file1.txt','file2.txt','file4.txt']; * * // asynchronous function that returns the file size in bytes * function getFileSizeInBytes(file, callback) { * fs.stat(file, function(err, stat) { * if (err) { * return callback(err); * } * callback(null, stat.size); * }); * } * * // Using callbacks * async.map(fileList, getFileSizeInBytes, function(err, results) { * if (err) { * console.log(err); * } else { * console.log(results); * // results is now an array of the file size in bytes for each file, e.g. * // [ 1000, 2000, 3000] * } * }); * * // Error Handling * async.map(withMissingFileList, getFileSizeInBytes, function(err, results) { * if (err) { * console.log(err); * // [ Error: ENOENT: no such file or directory ] * } else { * console.log(results); * } * }); * * // Using Promises * async.map(fileList, getFileSizeInBytes) * .then( results => { * console.log(results); * // results is now an array of the file size in bytes for each file, e.g. * // [ 1000, 2000, 3000] * }).catch( err => { * console.log(err); * }); * * // Error Handling * async.map(withMissingFileList, getFileSizeInBytes) * .then( results => { * console.log(results); * }).catch( err => { * console.log(err); * // [ Error: ENOENT: no such file or directory ] * }); * * // Using async/await * async () => { * try { * let results = await async.map(fileList, getFileSizeInBytes); * console.log(results); * // results is now an array of the file size in bytes for each file, e.g. * // [ 1000, 2000, 3000] * } * catch (err) { * console.log(err); * } * } * * // Error Handling * async () => { * try { * let results = await async.map(withMissingFileList, getFileSizeInBytes); * console.log(results); * } * catch (err) { * console.log(err); * // [ Error: ENOENT: no such file or directory ] * } * } * */ function map (coll, iteratee, callback) { return _asyncMap(eachOf$1, coll, iteratee, callback) } var map$1 = awaitify(map, 3); /** * Applies the provided arguments to each function in the array, calling * `callback` after all functions have completed. If you only provide the first * argument, `fns`, then it will return a function which lets you pass in the * arguments as if it were a single function call. If more arguments are * provided, `callback` is required while `args` is still optional. The results * for each of the applied async functions are passed to the final callback * as an array. * * @name applyEach * @static * @memberOf module:ControlFlow * @method * @category Control Flow * @param {Array|Iterable|AsyncIterable|Object} fns - A collection of {@link AsyncFunction}s * to all call with the same arguments * @param {...*} [args] - any number of separate arguments to pass to the * function. * @param {Function} [callback] - the final argument should be the callback, * called when all functions have completed processing. * @returns {AsyncFunction} - Returns a function that takes no args other than * an optional callback, that is the result of applying the `args` to each * of the functions. * @example * * const appliedFn = async.applyEach([enableSearch, updateSchema], 'bucket') * * appliedFn((err, results) => { * // results[0] is the results for `enableSearch` * // results[1] is the results for `updateSchema` * }); * * // partial application example: * async.each( * buckets, * async (bucket) => async.applyEach([enableSearch, updateSchema], bucket)(), * callback * ); */ var applyEach = applyEach$1(map$1); /** * The same as [`eachOf`]{@link module:Collections.eachOf} but runs only a single async operation at a time. * * @name eachOfSeries * @static * @memberOf module:Collections * @method * @see [async.eachOf]{@link module:Collections.eachOf} * @alias forEachOfSeries * @category Collection * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over. * @param {AsyncFunction} iteratee - An async function to apply to each item in * `coll`. * Invoked with (item, key, callback). * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. Invoked with (err). * @returns {Promise} a promise, if a callback is omitted */ function eachOfSeries(coll, iteratee, callback) { return eachOfLimit$1(coll, 1, iteratee, callback) } var eachOfSeries$1 = awaitify(eachOfSeries, 3); /** * The same as [`map`]{@link module:Collections.map} but runs only a single async operation at a time. * * @name mapSeries * @static * @memberOf module:Collections * @method * @see [async.map]{@link module:Collections.map} * @category Collection * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over. * @param {AsyncFunction} iteratee - An async function to apply to each item in * `coll`. * The iteratee should complete with the transformed item. * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. Results is an array of the * transformed items from the `coll`. Invoked with (err, results). * @returns {Promise} a promise, if no callback is passed */ function mapSeries (coll, iteratee, callback) { return _asyncMap(eachOfSeries$1, coll, iteratee, callback) } var mapSeries$1 = awaitify(mapSeries, 3); /** * The same as [`applyEach`]{@link module:ControlFlow.applyEach} but runs only a single async operation at a time. * * @name applyEachSeries * @static * @memberOf module:ControlFlow * @method * @see [async.applyEach]{@link module:ControlFlow.applyEach} * @category Control Flow * @param {Array|Iterable|AsyncIterable|Object} fns - A collection of {@link AsyncFunction}s to all * call with the same arguments * @param {...*} [args] - any number of separate arguments to pass to the * function. * @param {Function} [callback] - the final argument should be the callback, * called when all functions have completed processing. * @returns {AsyncFunction} - A function, that when called, is the result of * appling the `args` to the list of functions. It takes no args, other than * a callback. */ var applyEachSeries = applyEach$1(mapSeries$1); const PROMISE_SYMBOL = Symbol('promiseCallback'); function promiseCallback () { let resolve, reject; function callback (err, ...args) { if (err) return reject(err) resolve(args.length > 1 ? args : args[0]); } callback[PROMISE_SYMBOL] = new Promise((res, rej) => { resolve = res, reject = rej; }); return callback } /** * 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 {Promise} a promise, if a callback is not passed * @example * * //Using Callbacks * async.auto({ * get_data: function(callback) { * // async code to get some data * callback(null, 'data', 'converted to array'); * }, * make_folder: function(callback) { * // 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) { * // 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) { * // once the file is written let's email a link to it... * callback(null, {'file':results.write_file, 'email':'user@example.com'}); * }] * }, function(err, results) { * if (err) { * console.log('err = ', err); * } * console.log('results = ', results); * // results = { * // get_data: ['data', 'converted to array'] * // make_folder; 'folder', * // write_file: 'filename' * // email_link: { file: 'filename', email: 'user@example.com' } * // } * }); * * //Using Promises * 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) { * // 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) { * // once the file is written let's email a link to it... * callback(null, {'file':results.write_file, 'email':'user@example.com'}); * }] * }).then(results => { * console.log('results = ', results); * // results = { * // get_data: ['data', 'converted to array'] * // make_folder; 'folder', * // write_file: 'filename' * // email_link: { file: 'filename', email: 'user@example.com' } * // } * }).catch(err => { * console.log('err = ', err); * }); * * //Using async/await * async () => { * try { * let results = await async.auto({ * get_data: function(callback) { * // async code to get some data * callback(null, 'data', 'converted to array'); * }, * make_folder: function(callback) { * // 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) { * // 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) { * // once the file is written let's email a link to it... * callback(null, {'file':results.write_file, 'email':'user@example.com'}); * }] * }); * console.log('results = ', results); * // results = { * // get_data: ['data', 'converted to array'] * // make_folder; 'folder', * // write_file: 'filename' * // email_link: { file: 'filename', email: 'user@example.com' } * // } * } * catch (err) { * console.log(err); * } * } * */ function auto(tasks, concurrency, callback) { if (typeof concurrency !== 'number') { // concurrency is optional, shift the args. callback = concurrency; concurrency = null; } callback = once(callback || promiseCallback()); var numTasks = Object.keys(tasks).length; if (!numTasks) { return callback(null); } if (!concurrency) { concurrency = numTasks; } var results = {}; var runningTasks = 0; var canceled = false; 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 = {}; Object.keys(tasks).forEach(key => { var task = tasks[key]; if (!Array.isArray(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; dependencies.forEach(dependencyName => { if (!tasks[dependencyName]) { throw new Error('async.auto task `' + key + '` has a non-existent dependency `' + dependencyName + '` in ' + dependencies.join(', ')); } addListener(dependencyName, () => { remainingDependencies--; if (remainingDependencies === 0) { enqueueTask(key, task); } }); }); }); checkForDeadlocks(); processQueue(); function enqueueTask(key, task) { readyTasks.push(() => runTask(key, task)); } function processQueue() { if (canceled) return 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] || []; taskListeners.forEach(fn => fn()); processQueue(); } function runTask(key, task) { if (hasError) return; var taskCallback = onlyOnce((err, ...result) => { runningTasks--; if (err === false) { canceled = true; return } if (result.length < 2) { [result] = result; } if (err) { var safeResults = {}; Object.keys(results).forEach(rkey => { safeResults[rkey] = results[rkey]; }); safeResults[key] = result; hasError = true; listeners = Object.create(null); if (canceled) return callback(err, safeResults); } else { results[key] = result; taskComplete(key); } }); runningTasks++; var taskFn = wrapAsync(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++; getDependents(currentTask).forEach(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 = []; Object.keys(tasks).forEach(key => { const task = tasks[key]; if (Array.isArray(task) && task.indexOf(taskName) >= 0) { result.push(key); } }); return result; } return callback[PROMISE_SYMBOL] } var FN_ARGS = /^(?:async\s)?(?:function)?\s*(?:\w+\s*)?\(([^)]+)\)(?:\s*{)/; var ARROW_FN_ARGS = /^(?:async\s)?\s*(?:\(\s*)?((?:[^)=\s]\s*)*)(?:\)\s*)?=>/; var FN_ARG_SPLIT = /,/; var FN_ARG = /(=.+)?(\s*)$/; function stripComments(string) { let stripped = ''; let index = 0; let endBlockComment = string.indexOf('*/'); while (index < string.length) { if (string[index] === '/' && string[index+1] === '/') { // inline comment let endIndex = string.indexOf('\n', index); index = (endIndex === -1) ? string.length : endIndex; } else if ((endBlockComment !== -1) && (string[index] === '/') && (string[index+1] === '*')) { // block comment let endIndex = string.indexOf('*/', index); if (endIndex !== -1) { index = endIndex + 2; endBlockComment = string.indexOf('*/', index); } else { stripped += string[index]; index++; } } else { stripped += string[index]; index++; } } return stripped; } function parseParams(func) { const src = stripComments(func.toString()); let match = src.match(FN_ARGS); if (!match) { match = src.match(ARROW_FN_ARGS); } if (!match) throw new Error('could not parse args in autoInject\nSource:\n' + src) let [, args] = match; return args .replace(/\s/g, '') .split(FN_ARG_SPLIT) .map((arg) => arg.replace(FN_ARG, '').trim()); } /** * A dependency-injected version of the [async.auto]{@link module:ControlFlow.auto} function. Dependent * tasks are specified as parameters to the function, after the usual callback * parameter, with the parameter names matching the names of the tasks it * depends on. This can provide even more readable task graphs which can be * easier to maintain. * * If a final callback is specified, the task results are similarly injected, * specified as named parameters after the initial error parameter. * * The autoInject function is purely syntactic sugar and its semantics are * otherwise equivalent to [async.auto]{@link module:ControlFlow.auto}. * * @name autoInject * @static * @memberOf module:ControlFlow * @method * @see [async.auto]{@link module:ControlFlow.auto} * @category Control Flow * @param {Object} tasks - An object, each of whose properties is an {@link AsyncFunction} of * the form 'func([dependencies...], callback). 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 `callback` parameter is a `callback(err, result)` which must be called * when finished, passing an `error` (which can be `null`) and the result of * the function's execution. The remaining parameters name other tasks on * which the task is dependent, and the results from those tasks are the * arguments of those parameters. * @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, and a `results` object with any completed * task results, similar to `auto`. * @returns {Promise} a promise, if no callback is passed * @example * * // The example from `auto` can be rewritten as follows: * async.autoInject({ * get_data: function(callback) { * // async code to get some data * callback(null, 'data', 'converted to array'); * }, * make_folder: function(callback) { * // 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: function(get_data, make_folder, callback) { * // once there is some data and the directory exists, * // write the data to a file in the directory * callback(null, 'filename'); * }, * email_link: function(write_file, callback) { * // once the file is written let's email a link to it... * // write_file contains the filename returned by write_file. * callback(null, {'file':write_file, 'email':'user@example.com'}); * } * }, function(err, results) { * console.log('err = ', err); * console.log('email_link = ', results.email_link); * }); * * // If you are using a JS minifier that mangles parameter names, `autoInject` * // will not work with plain functions, since the parameter names will be * // collapsed to a single letter identifier. To work around this, you can * // explicitly specify the names of the parameters your task function needs * // in an array, similar to Angular.js dependency injection. * * // This still has an advantage over plain `auto`, since the results a task * // depends on are still spread into arguments. * async.autoInject({ * //... * write_file: ['get_data', 'make_folder', function(get_data, make_folder, callback) { * callback(null, 'filename'); * }], * email_link: ['write_file', function(write_file, callback) { * callback(null, {'file':write_file, 'email':'user@example.com'}); * }] * //... * }, function(err, results) { * console.log('err = ', err); * console.log('email_link = ', results.email_link); * }); */ function autoInject(tasks, callback) { var newTasks = {}; Object.keys(tasks).forEach(key => { var taskFn = tasks[key]; var params; var fnIsAsync = isAsync(taskFn); var hasNoDeps = (!fnIsAsync && taskFn.length === 1) || (fnIsAsync && taskFn.length === 0); if (Array.isArray(taskFn)) { params = [...taskFn]; taskFn = params.pop(); newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn); } else if (hasNoDeps) { // no dependencies, use the function as-is newTasks[key] = taskFn; } else { params = parseParams(taskFn); if ((taskFn.length === 0 && !fnIsAsync) && params.length === 0) { throw new Error("autoInject task functions require explicit parameters."); } // remove callback param if (!fnIsAsync) params.pop(); newTasks[key] = params.concat(newTask); } function newTask(results, taskCb) { var newAr