From 9fc2995f581e500dbe9a54939feeaf11b0655b28 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Wed, 22 Mar 2017 13:20:15 -0700 Subject: [PATCH 01/16] initial experiment with async fn support --- .eslintrc | 2 +- .travis.yml | 4 +- lib/asyncify.js | 2 +- lib/each.js | 3 +- lib/eachLimit.js | 3 +- lib/eachOf.js | 3 +- lib/eachOfLimit.js | 3 +- lib/internal/filter.js | 4 +- lib/internal/map.js | 4 +- lib/internal/wrapAsync.js | 11 ++++ mocha_test/asyncFunctions.js | 20 +++++++ mocha_test/es2017/asyncFunctions.js | 86 +++++++++++++++++++++++++++++ package.json | 5 +- 13 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 lib/internal/wrapAsync.js create mode 100644 mocha_test/asyncFunctions.js create mode 100644 mocha_test/es2017/asyncFunctions.js diff --git a/.eslintrc b/.eslintrc index 2644f5cc6..2627332f2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,7 +6,7 @@ "es6": true }, "parserOptions": { - "ecmaVersion": 6, + "ecmaVersion": 8, "sourceType": "module" }, "rules": { diff --git a/.travis.yml b/.travis.yml index ff46739c1..989efb642 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ node_js: - "0.10" - "0.12" - "4" + - "6" + - "7" matrix: include: @@ -27,4 +29,4 @@ script: # ensure buildable - "[ $MAKE_TEST == false ] || make" # test in firefox - - "[ $BROWSER == false ] || npm run mocha-browser-test" \ No newline at end of file + - "[ $BROWSER == false ] || npm run mocha-browser-test" diff --git a/lib/asyncify.js b/lib/asyncify.js index dec39d239..61794fb05 100644 --- a/lib/asyncify.js +++ b/lib/asyncify.js @@ -48,7 +48,7 @@ import initialParams from './internal/initialParams'; * } * ], callback); * - * // es6 example + * // es2017 example * var q = async.queue(async.asyncify(async function(file) { * var intermediateStep = await processFile(file); * return await somePromise(intermediateStep) diff --git a/lib/each.js b/lib/each.js index 5bf6bd4d6..e20b96516 100644 --- a/lib/each.js +++ b/lib/each.js @@ -1,5 +1,6 @@ import eachOf from './eachOf'; import withoutIndex from './internal/withoutIndex'; +import wrapAsync from './internal/wrapAsync' /** * Applies the function `iteratee` to each item in `coll`, in parallel. @@ -61,5 +62,5 @@ import withoutIndex from './internal/withoutIndex'; * }); */ export default function eachLimit(coll, iteratee, callback) { - eachOf(coll, withoutIndex(iteratee), callback); + eachOf(coll, withoutIndex(wrapAsync(iteratee)), callback); } diff --git a/lib/eachLimit.js b/lib/eachLimit.js index e2ed79088..c3bc595d0 100644 --- a/lib/eachLimit.js +++ b/lib/eachLimit.js @@ -1,5 +1,6 @@ import eachOfLimit from './internal/eachOfLimit'; import withoutIndex from './internal/withoutIndex'; +import wrapAsync from './internal/wrapAsync'; /** * The same as [`each`]{@link module:Collections.each} but runs a maximum of `limit` async operations at a time. @@ -23,5 +24,5 @@ import withoutIndex from './internal/withoutIndex'; * `iteratee` functions have finished, or an error occurs. Invoked with (err). */ export default function eachLimit(coll, limit, iteratee, callback) { - eachOfLimit(limit)(coll, withoutIndex(iteratee), callback); + eachOfLimit(limit)(coll, withoutIndex(wrapAsync(iteratee)), callback); } diff --git a/lib/eachOf.js b/lib/eachOf.js index 798f270ac..1cfa171dc 100644 --- a/lib/eachOf.js +++ b/lib/eachOf.js @@ -6,6 +6,7 @@ import doLimit from './internal/doLimit'; import noop from 'lodash/noop'; import once from './internal/once'; import onlyOnce from './internal/onlyOnce'; +import wrapAsync from './internal/wrapAsync'; // eachOf implementation optimized for array-likes function eachOfArrayLike(coll, iteratee, callback) { @@ -76,5 +77,5 @@ var eachOfGeneric = doLimit(eachOfLimit, Infinity); */ export default function(coll, iteratee, callback) { var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric; - eachOfImplementation(coll, iteratee, callback); + eachOfImplementation(coll, wrapAsync(iteratee), callback); } diff --git a/lib/eachOfLimit.js b/lib/eachOfLimit.js index c3b4567fd..17dffc11b 100644 --- a/lib/eachOfLimit.js +++ b/lib/eachOfLimit.js @@ -1,4 +1,5 @@ import _eachOfLimit from './internal/eachOfLimit'; +import wrapAsync from './internal/wrapAsync'; /** * The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a @@ -23,5 +24,5 @@ import _eachOfLimit from './internal/eachOfLimit'; * `iteratee` functions have finished, or an error occurs. Invoked with (err). */ export default function eachOfLimit(coll, limit, iteratee, callback) { - _eachOfLimit(limit)(coll, iteratee, callback); + _eachOfLimit(limit)(coll, wrapAsync(iteratee), callback); } diff --git a/lib/internal/filter.js b/lib/internal/filter.js index 5af788c5d..032bd336b 100644 --- a/lib/internal/filter.js +++ b/lib/internal/filter.js @@ -3,6 +3,8 @@ import isArrayLike from 'lodash/isArrayLike'; import property from 'lodash/_baseProperty'; import noop from 'lodash/noop'; +import wrapAsync from './wrapAsync'; + function filterArray(eachfn, arr, iteratee, callback) { var truthValues = new Array(arr.length); eachfn(arr, function (x, index, callback) { @@ -46,5 +48,5 @@ function filterGeneric(eachfn, coll, iteratee, callback) { export default function _filter(eachfn, coll, iteratee, callback) { var filter = isArrayLike(coll) ? filterArray : filterGeneric; - filter(eachfn, coll, iteratee, callback || noop); + filter(eachfn, coll, wrapAsync(iteratee), callback || noop); } diff --git a/lib/internal/map.js b/lib/internal/map.js index 453cd29be..9cd927e2c 100644 --- a/lib/internal/map.js +++ b/lib/internal/map.js @@ -1,14 +1,16 @@ import noop from 'lodash/noop'; +import wrapAsync from './wrapAsync'; export default function _asyncMap(eachfn, arr, iteratee, callback) { callback = callback || noop; arr = arr || []; var results = []; var counter = 0; + var _iteratee = wrapAsync(iteratee); eachfn(arr, function (value, _, callback) { var index = counter++; - iteratee(value, function (err, v) { + _iteratee(value, function (err, v) { results[index] = v; callback(err); }); diff --git a/lib/internal/wrapAsync.js b/lib/internal/wrapAsync.js new file mode 100644 index 000000000..90e07020c --- /dev/null +++ b/lib/internal/wrapAsync.js @@ -0,0 +1,11 @@ +import asyncify from '../asyncify'; + +var supportsSymbol = typeof Symbol !== 'undefined'; + +export default function wrapAsync(asyncFn) { + if (!supportsSymbol) return asyncFn; + + var isAsync = asyncFn[Symbol.toStringTag] === 'AsyncFunction'; + + return isAsync ? asyncify(asyncFn) : asyncFn; +} diff --git a/mocha_test/asyncFunctions.js b/mocha_test/asyncFunctions.js new file mode 100644 index 000000000..67e1b161e --- /dev/null +++ b/mocha_test/asyncFunctions.js @@ -0,0 +1,20 @@ +describe('async function support', function () { + this.timeout(100); + + var supportsAsync; + var supportsSymbol = typeof Symbol !== 'undefined'; + try { + /* eslint no-eval:0 */ + var fn = eval('(async function() {})') + supportsAsync = supportsSymbol && + fn[Symbol.toStringTag] === 'AsyncFunction'; + } catch (e) { + supportsAsync = false; + } + + if (supportsAsync) { + require('./es2017/asyncFunctions.js')(); + } else { + it('should not test async functions in this environment'); + } +}); diff --git a/mocha_test/es2017/asyncFunctions.js b/mocha_test/es2017/asyncFunctions.js new file mode 100644 index 000000000..46bd6ca7c --- /dev/null +++ b/mocha_test/es2017/asyncFunctions.js @@ -0,0 +1,86 @@ +var async = require('../../lib'); +const expect = require('chai').expect; +const assert = require('assert'); + + +module.exports = function () { + async function asyncIdentity(val) { + var res = await Promise.resolve(val); + return res; + } + + const input = [1, 2, 3]; + + it('should asyncify async functions', (done) => { + async.asyncify(asyncIdentity)(42, (err, val) => { + assert(val === 42); + done(err); + }) + }); + + it('should handle async functions in each', (done) => { + async.each(input, asyncIdentity, done); + }); + + it('should handle async functions in eachLimit', (done) => { + async.eachLimit(input, 2, asyncIdentity, done); + }); + + it('should handle async functions in eachSeries', (done) => { + async.eachSeries(input, asyncIdentity, done); + }); + + it('should handle async functions in eachOf', (done) => { + async.eachOf(input, asyncIdentity, done); + }); + + it('should handle async functions in eachOfLimit', (done) => { + async.eachOfLimit(input, 2, asyncIdentity, done); + }); + + it('should handle async functions in eachOfSeries', (done) => { + async.eachOfSeries(input, asyncIdentity, done); + }); + + it('should handle async functions in map', (done) => { + async.map(input, asyncIdentity, (err, result) => { + expect(result).to.eql(input); + done(err); + }); + }); + + it('should handle async functions in mapLimit', (done) => { + async.mapLimit(input, 2, asyncIdentity, (err, result) => { + expect(result).to.eql(input); + done(err); + }); + }); + + it('should handle async functions in mapSeries', (done) => { + async.mapSeries(input, asyncIdentity, (err, result) => { + expect(result).to.eql(input); + done(err); + }); + }); + + it('should handle async functions in filter', (done) => { + async.filter(input, asyncIdentity, (err, result) => { + expect(result).to.eql(input); + done(err); + }); + }); + + it('should handle async functions in filterLimit', (done) => { + async.filterLimit(input, 2, asyncIdentity, (err, result) => { + expect(result).to.eql(input); + done(err); + }); + }); + + it('should handle async functions in filterSeries', (done) => { + async.filterSeries(input, asyncIdentity, (err, result) => { + expect(result).to.eql(input); + done(err); + }); + }); +} diff --git a/package.json b/package.json index 1082cb66c..6e9427a69 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,13 @@ "lodash-es": "^4.14.0" }, "devDependencies": { - "babel-cli": "^6.16.0", - "babel-core": "^6.3.26", + "babel-cli": "^6.24.0", + "babel-core": "^6.24.0", "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-istanbul": "^2.0.1", "babel-plugin-transform-es2015-modules-commonjs": "^6.3.16", "babel-preset-es2015": "^6.3.13", + "babel-preset-es2017": "^6.22.0", "babelify": "^7.2.0", "benchmark": "^2.1.1", "bluebird": "^3.4.6", From 77e6a212f9ab00f2d191b5c8128667105eb4aafd Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Wed, 22 Mar 2017 13:38:20 -0700 Subject: [PATCH 02/16] disable browser test for now --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 989efb642..267defc44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,12 @@ node_js: - "6" - "7" -matrix: - include: - - node_js: "6" - addons: - firefox: "49.0" - env: BROWSER=true MAKE_TEST=true +#matrix: +# include: +# - node_js: "6" +# addons: +# firefox: "49.0" +# env: BROWSER=true MAKE_TEST=true env: matrix: BROWSER=false MAKE_TEST=false @@ -20,9 +20,9 @@ after_success: npm run coveralls # Needed to run Karma with Firefox on Travis # http://karma-runner.github.io/0.13/plus/travis.html -before_script: - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start +#before_script: +# - export DISPLAY=:99.0 +# - sh -e /etc/init.d/xvfb start script: - npm test From ac0bdd00c779eb38353b6c30c6b106b3c74ae8f2 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Wed, 22 Mar 2017 13:52:29 -0700 Subject: [PATCH 03/16] factor out async support check, make wrapAsync identity() in non-supported envs --- dist/async.js | 329 +++++++++++++++++++++++++---------- dist/async.min.js | 2 +- dist/async.min.map | 2 +- lib/internal/wrapAsync.js | 27 ++- mocha_test/asyncFunctions.js | 15 +- 5 files changed, 260 insertions(+), 115 deletions(-) diff --git a/dist/async.js b/dist/async.js index e889904ce..6e4ef324e 100644 --- a/dist/async.js +++ b/dist/async.js @@ -200,7 +200,8 @@ function baseGetTag(value) { if (value == null) { return value === undefined ? undefinedTag : nullTag; } - return (symToStringTag && symToStringTag in Object(value)) + value = Object(value); + return (symToStringTag && symToStringTag in value) ? getRawTag(value) : objectToString(value); } @@ -639,7 +640,7 @@ var freeProcess = moduleExports$1 && freeGlobal.process; /** Used to access faster Node.js helpers. */ var nodeUtil = (function() { try { - return freeProcess && freeProcess.binding && freeProcess.binding('util'); + return freeProcess && freeProcess.binding('util'); } catch (e) {} }()); @@ -886,6 +887,105 @@ function _eachOfLimit(limit) { }; } +/** + * 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 ES2016 `async` functions. + * + * @name asyncify + * @static + * @memberOf module:Utils + * @method + * @alias wrapSync + * @category Util + * @param {Function} func - The synchronous function to convert to an + * asynchronous function. + * @returns {Function} An asynchronous wrapper of the `func`. To be invoked with + * (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 + * var q = async.queue(async.asyncify(async function(file) { + * var intermediateStep = await processFile(file); + * return await somePromise(intermediateStep) + * })); + * + * q.push(files); + */ +function asyncify(func) { + return initialParams(function (args, callback) { + var result; + try { + result = func.apply(this, args); + } catch (e) { + return callback(e); + } + // if result is Promise object + if (isObject(result) && typeof result.then === 'function') { + result.then(function (value) { + callback(null, value); + }, function (err) { + callback(err.message ? err : new Error(err)); + }); + } else { + callback(null, result); + } + }); +} + +var supportsSymbol = typeof Symbol !== 'undefined'; + +function supportsAsync() { + var supported; + try { + /* eslint no-eval: 0 */ + supported = supportsSymbol && isAsync(eval('(async function () {})')); + } catch (e) { + supported = false; + } + return supported; +} + +function isAsync(fn) { + return fn[Symbol.toStringTag] === 'AsyncFunction'; +} + +var wrapAsync$1 = supportsAsync() ? function wrapAsync(asyncFn) { + if (!supportsSymbol) return asyncFn; + + return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn; +} : identity; + /** * The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a * time. @@ -909,7 +1009,7 @@ function _eachOfLimit(limit) { * `iteratee` functions have finished, or an error occurs. Invoked with (err). */ function eachOfLimit(coll, limit, iteratee, callback) { - _eachOfLimit(limit)(coll, iteratee, callback); + _eachOfLimit(limit)(coll, wrapAsync$1(iteratee), callback); } function doLimit(fn, limit) { @@ -987,7 +1087,7 @@ var eachOfGeneric = doLimit(eachOfLimit, Infinity); */ var eachOf = function (coll, iteratee, callback) { var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric; - eachOfImplementation(coll, iteratee, callback); + eachOfImplementation(coll, wrapAsync$1(iteratee), callback); }; function doParallel(fn) { @@ -1001,10 +1101,11 @@ function _asyncMap(eachfn, arr, iteratee, callback) { arr = arr || []; var results = []; var counter = 0; + var _iteratee = wrapAsync$1(iteratee); eachfn(arr, function (value, _, callback) { var index = counter++; - iteratee(value, function (err, v) { + _iteratee(value, function (err, v) { results[index] = v; callback(err); }); @@ -1028,7 +1129,7 @@ function _asyncMap(eachfn, arr, iteratee, callback) { * * 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) + * vary across JavaScript engines). * * @name map * @static @@ -1204,82 +1305,6 @@ var apply$2 = rest(function (fn, args) { }); }); -/** - * 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 ES2016 `async` functions. - * - * @name asyncify - * @static - * @memberOf module:Utils - * @method - * @alias wrapSync - * @category Util - * @param {Function} func - The synchronous function to convert to an - * asynchronous function. - * @returns {Function} An asynchronous wrapper of the `func`. To be invoked with - * (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); - * - * // es6 example - * var q = async.queue(async.asyncify(async function(file) { - * var intermediateStep = await processFile(file); - * return await somePromise(intermediateStep) - * })); - * - * q.push(files); - */ -function asyncify(func) { - return initialParams(function (args, callback) { - var result; - try { - result = func.apply(this, args); - } catch (e) { - return callback(e); - } - // if result is Promise object - if (isObject(result) && typeof result.then === 'function') { - result.then(function (value) { - callback(null, value); - }, function (err) { - callback(err.message ? err : new Error(err)); - }); - } else { - callback(null, result); - } - }); -} - /** * A specialized version of `_.forEach` for arrays without support for * iteratee shorthands. @@ -1827,17 +1852,15 @@ function asciiToArray(string) { /** Used to compose unicode character classes. */ var rsAstralRange = '\\ud800-\\udfff'; -var rsComboMarksRange = '\\u0300-\\u036f'; -var reComboHalfMarksRange = '\\ufe20-\\ufe2f'; -var rsComboSymbolsRange = '\\u20d0-\\u20ff'; -var rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange; +var rsComboMarksRange = '\\u0300-\\u036f\\ufe20-\\ufe23'; +var rsComboSymbolsRange = '\\u20d0-\\u20f0'; var rsVarRange = '\\ufe0e\\ufe0f'; /** Used to compose unicode capture groups. */ var rsZWJ = '\\u200d'; /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */ -var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']'); +var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboMarksRange + rsComboSymbolsRange + rsVarRange + ']'); /** * Checks if `string` contains Unicode symbols. @@ -1852,15 +1875,13 @@ function hasUnicode(string) { /** Used to compose unicode character classes. */ var rsAstralRange$1 = '\\ud800-\\udfff'; -var rsComboMarksRange$1 = '\\u0300-\\u036f'; -var reComboHalfMarksRange$1 = '\\ufe20-\\ufe2f'; -var rsComboSymbolsRange$1 = '\\u20d0-\\u20ff'; -var rsComboRange$1 = rsComboMarksRange$1 + reComboHalfMarksRange$1 + rsComboSymbolsRange$1; +var rsComboMarksRange$1 = '\\u0300-\\u036f\\ufe20-\\ufe23'; +var rsComboSymbolsRange$1 = '\\u20d0-\\u20f0'; var rsVarRange$1 = '\\ufe0e\\ufe0f'; /** Used to compose unicode capture groups. */ var rsAstral = '[' + rsAstralRange$1 + ']'; -var rsCombo = '[' + rsComboRange$1 + ']'; +var rsCombo = '[' + rsComboMarksRange$1 + rsComboSymbolsRange$1 + ']'; var rsFitz = '\\ud83c[\\udffb-\\udfff]'; var rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')'; var rsNonAstral = '[^' + rsAstralRange$1 + ']'; @@ -3086,7 +3107,7 @@ function _withoutIndex(iteratee) { * }); */ function eachLimit(coll, iteratee, callback) { - eachOf(coll, _withoutIndex(iteratee), callback); + eachOf(coll, _withoutIndex(wrapAsync$1(iteratee)), callback); } /** @@ -3111,7 +3132,7 @@ function eachLimit(coll, iteratee, callback) { * `iteratee` functions have finished, or an error occurs. Invoked with (err). */ function eachLimit$1(coll, limit, iteratee, callback) { - _eachOfLimit(limit)(coll, _withoutIndex(iteratee), callback); + _eachOfLimit(limit)(coll, _withoutIndex(wrapAsync$1(iteratee)), callback); } /** @@ -3321,7 +3342,7 @@ function filterGeneric(eachfn, coll, iteratee, callback) { function _filter(eachfn, coll, iteratee, callback) { var filter = isArrayLike(coll) ? filterArray : filterGeneric; - filter(eachfn, coll, iteratee, callback || noop); + filter(eachfn, coll, wrapAsync$1(iteratee), callback || noop); } /** @@ -3433,6 +3454,114 @@ function forever(fn, errback) { next(); } +/** + * The same as [`groupBy`]{@link module:Collections.groupBy} but runs a maximum of `limit` async operations at a time. + * + * @name groupByLimit + * @static + * @memberOf module:Collections + * @method + * @see [async.groupBy]{@link module:Collections.groupBy} + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - A function to apply to each item in `coll`. + * The iteratee is passed a `callback(err, key)` which must be called once it + * has completed with an error (which can be `null`) and the `key` to group the + * value under. Invoked with (value, callback). + * @param {Function} [callback] - A callback which is called when all `iteratee` + * functions have finished, or an error occurs. Result is an `Object` whoses + * properties are arrays of values which returned the corresponding key. + */ +var groupByLimit = function (coll, limit, iteratee, callback) { + callback = callback || noop; + + mapLimit(coll, limit, function (val, callback) { + iteratee(val, function (err, key) { + if (err) return callback(err); + return callback(null, { key: key, val: val }); + }); + }, function (err, mapResults) { + var result = {}; + // from MDN, handle object having an `hasOwnProperty` prop + var hasOwnProperty = Object.prototype.hasOwnProperty; + + for (var i = 0; i < mapResults.length; i++) { + if (mapResults[i]) { + var key = mapResults[i].key; + var val = mapResults[i].val; + + if (hasOwnProperty.call(result, key)) { + result[key].push(val); + } else { + result[key] = [val]; + } + } + } + + return callback(err, result); + }); +}; + +/** + * Returns a new object, where each value corresponds to an array of items, from + * `coll`, that returned the corresponding key. That is, the keys of the object + * correspond to the values passed to the `iteratee` callback. + * + * Note: 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 values for each key in the `result` will be in the same order as + * the original `coll`. For Objects, the values will roughly be in the order of + * the original Objects' keys (but this can vary across JavaScript engines). + * + * @name groupBy + * @static + * @memberOf module:Collections + * @method + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A function to apply to each item in `coll`. + * The iteratee is passed a `callback(err, key)` which must be called once it + * has completed with an error (which can be `null`) and the `key` to group the + * value under. Invoked with (value, callback). + * @param {Function} [callback] - A callback which is called when all `iteratee` + * functions have finished, or an error occurs. Result is an `Object` whoses + * properties are arrays of values which returned the corresponding key. + * @example + * + * async.groupBy(['userId1', 'userId2', 'userId3'], function(userId, callback) { + * db.findById(userId, function(err, user) { + * if (err) return callback(err); + * return callback(null, user.age); + * }); + * }, function(err, result) { + * // result is object containing the userIds grouped by age + * // e.g. { 30: ['userId1', 'userId3'], 42: ['userId2']}; + * }); + */ +var groupBy = doLimit(groupByLimit, Infinity); + +/** + * The same as [`groupBy`]{@link module:Collections.groupBy} but runs only a single async operation at a time. + * + * @name groupBySeries + * @static + * @memberOf module:Collections + * @method + * @see [async.groupBy]{@link module:Collections.groupBy} + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - A function to apply to each item in `coll`. + * The iteratee is passed a `callback(err, key)` which must be called once it + * has completed with an error (which can be `null`) and the `key` to group the + * value under. Invoked with (value, callback). + * @param {Function} [callback] - A callback which is called when all `iteratee` + * functions have finished, or an error occurs. Result is an `Object` whoses + * properties are arrays of values which returned the corresponding key. + */ +var groupBySeries = doLimit(groupByLimit, 1); + /** * Logs the result of an `async` function to the `console`. Only works in * Node.js or in browsers that support `console.log` and `console.error` (such @@ -3707,6 +3836,8 @@ function _parallel(eachfn, tasks, callback) { * any I/O, they will actually be executed in series. Any synchronous setup * sections for each task will happen one after the other. JavaScript remains * single-threaded. + * **Hint:** Use [`reflect`]{@link module:Utils.reflect} to continue the + * execution of other tasks when a task fails. * * It is also possible to use an object instead of an array. Each property will * be run as a function and the results will be passed to the final `callback` @@ -4881,7 +5012,7 @@ var timesSeries = doLimit(timeLimit, 1); * }) */ function transform(coll, accumulator, iteratee, callback) { - if (arguments.length === 3) { + if (arguments.length <= 3) { callback = iteratee; iteratee = accumulator; accumulator = isArray(coll) ? [] : {}; @@ -5130,6 +5261,9 @@ var index = { filterLimit: filterLimit, filterSeries: filterSeries, forever: forever, + groupBy: groupBy, + groupByLimit: groupByLimit, + groupBySeries: groupBySeries, log: log, map: map, mapLimit: mapLimit, @@ -5222,6 +5356,9 @@ exports.filter = filter; exports.filterLimit = filterLimit; exports.filterSeries = filterSeries; exports.forever = forever; +exports.groupBy = groupBy; +exports.groupByLimit = groupByLimit; +exports.groupBySeries = groupBySeries; exports.log = log; exports.map = map; exports.mapLimit = mapLimit; diff --git a/dist/async.min.js b/dist/async.min.js index 469ae578c..542756750 100644 --- a/dist/async.min.js +++ b/dist/async.min.js @@ -1,2 +1,2 @@ -!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(n.async=n.async||{})}(this,function(n){"use strict";function t(n,t,e){switch(e.length){case 0:return n.call(t);case 1:return n.call(t,e[0]);case 2:return n.call(t,e[0],e[1]);case 3:return n.call(t,e[0],e[1],e[2])}return n.apply(t,e)}function e(n,e,r){return e=rt(void 0===e?n.length-1:e,0),function(){for(var u=arguments,o=-1,i=rt(u.length-e,0),c=Array(i);++o-1&&n%1==0&&n<=kt}function p(n){return null!=n&&s(n.length)&&!l(n)}function h(){}function y(n){return function(){if(null!==n){var t=n;n=null,t.apply(this,arguments)}}}function v(n,t){for(var e=-1,r=Array(n);++e-1&&n%1==0&&nu?0:u+t),e=e>u?u:e,e<0&&(e+=u),u=t>e?0:e-t>>>0,t>>>=0;for(var o=Array(u);++r=r?n:K(n,t,e)}function Y(n,t){for(var e=n.length;e--&&Q(t,n[e],0)>-1;);return e}function Z(n,t){for(var e=-1,r=n.length;++e-1;);return e}function nn(n){return n.split("")}function tn(n){return Qe.test(n)}function en(n){return n.match(pr)||[]}function rn(n){return tn(n)?en(n):nn(n)}function un(n){return null==n?"":J(n)}function on(n,t,e){if(n=un(n),n&&(e||void 0===t))return n.replace(hr,"");if(!n||!(t=J(t)))return n;var r=rn(n),u=rn(t),o=Z(r,u),i=Y(r,u)+1;return X(r,o,i).join("")}function cn(n){return n=n.toString().replace(mr,""),n=n.match(yr)[2].replace(" ",""),n=n?n.split(vr):[],n=n.map(function(n){return on(n.replace(dr,""))})}function fn(n,t){var e={};C(n,function(n,t){function r(t,e){var r=G(u,function(n){return t[n]});r.push(e),n.apply(null,r)}var u;if(It(n))u=n.slice(0,-1),n=n[n.length-1],e[t]=u.concat(u.length>0?r:n);else if(1===n.length)e[t]=n;else{if(u=cn(n),0===n.length&&0===u.length)throw new Error("autoInject task functions require explicit parameters.");u.pop(),e[t]=u.concat(r)}}),Me(e,t)}function an(n){setTimeout(n,0)}function ln(n){return u(function(t,e){n(function(){t.apply(null,e)})})}function sn(){this.head=this.tail=null,this.length=0}function pn(n,t){n.length=1,n.head=n.tail=t}function hn(n,t,e){function r(n,t,e){if(null!=e&&"function"!=typeof e)throw new Error("task callback must be a function");if(a.started=!0,It(n)||(n=[n]),0===n.length&&a.idle())return jr(function(){a.drain()});for(var r=0,u=n.length;r=0&&c.splice(o),u.callback.apply(u,t),null!=t[0]&&a.error(t[0],u.data)}i<=a.concurrency-a.buffer&&a.unsaturated(),a.idle()&&a.drain(),a.process()})}if(null==t)t=1;else if(0===t)throw new Error("Concurrency must not be zero");var i=0,c=[],f=!1,a={_tasks:new sn,concurrency:t,payload:e,saturated:h,unsaturated:h,buffer:t/4,empty:h,drain:h,error:h,started:!1,paused:!1,push:function(n,t){r(n,!1,t)},kill:function(){a.drain=h,a._tasks.empty()},unshift:function(n,t){r(n,!0,t)},process:function(){if(!f){for(f=!0;!a.paused&&i1&&(r=t),e(null,{value:r})}})),n.apply(this,t)})}function Wn(n,t,e,r){Bn(n,t,function(n,t){e(n,function(n,e){t(n,!e)})},r)}function Nn(n){var t;return It(n)?t=G(n,$n):(t={},C(n,function(n,e){t[e]=$n.call(this,n)})),t}function Qn(n){return function(){return n}}function Gn(n,t,e){function r(n,t){if("object"==typeof t)n.times=+t.times||o,n.intervalFunc="function"==typeof t.interval?t.interval:Qn(+t.interval||i),n.errorFilter=t.errorFilter;else{if("number"!=typeof t&&"string"!=typeof t)throw new Error("Invalid arguments for async.retry");n.times=+t||o}}function u(){t(function(n){n&&f++r?1:0}Le(n,function(n,e){t(n,function(t,r){return t?e(t):void e(null,{value:n,criteria:r})})},function(n,t){return n?e(n):void e(null,G(t.sort(r),Fn("value")))})}function Kn(n,t,e){function r(){c||(o.apply(null,arguments),clearTimeout(i))}function u(){var t=n.name||"anonymous",r=new Error('Callback function "'+t+'" timed out.');r.code="ETIMEDOUT",e&&(r.info=e),c=!0,o(r)}var o,i,c=!1;return ut(function(e,c){o=c,i=setTimeout(u,t),n.apply(null,e.concat(r))})}function Xn(n,t,e,r){for(var u=-1,o=nu(Zr((t-n)/(e||1)),0),i=Array(o);o--;)i[r?o:++u]=n,n+=e;return i}function Yn(n,t,e,r){Te(Xn(0,n,1),t,e,r)}function Zn(n,t,e,r){3===arguments.length&&(r=e,e=t,t=It(n)?[]:{}),r=y(r||h),Ee(n,function(n,r,u){e(t,n,r,u)},function(n){r(n,t)})}function nt(n){return function(){return(n.unmemoized||n).apply(null,arguments)}}function tt(n,t,e){if(e=I(e||h),!n())return e(null);var r=u(function(u,o){return u?e(u):n()?t(r):void e.apply(null,[null].concat(o))});t(r)}function et(n,t,e){tt(function(){return!n.apply(this,arguments)},t,e)}var rt=Math.max,ut=function(n){return u(function(t){var e=t.pop();n.call(this,t,e)})},ot="object"==typeof global&&global&&global.Object===Object&&global,it="object"==typeof self&&self&&self.Object===Object&&self,ct=ot||it||Function("return this")(),ft=ct.Symbol,at=Object.prototype,lt=at.hasOwnProperty,st=at.toString,pt=ft?ft.toStringTag:void 0,ht=Object.prototype,yt=ht.toString,vt="[object Null]",dt="[object Undefined]",mt=ft?ft.toStringTag:void 0,gt="[object AsyncFunction]",bt="[object Function]",jt="[object GeneratorFunction]",St="[object Proxy]",kt=9007199254740991,wt={},Ot="function"==typeof Symbol&&Symbol.iterator,xt=function(n){return Ot&&n[Ot]&&n[Ot]()},Et="[object Arguments]",Lt=Object.prototype,At=Lt.hasOwnProperty,Tt=Lt.propertyIsEnumerable,Ft=m(function(){return arguments}())?m:function(n){return d(n)&&At.call(n,"callee")&&!Tt.call(n,"callee")},It=Array.isArray,_t="object"==typeof n&&n&&!n.nodeType&&n,Bt=_t&&"object"==typeof module&&module&&!module.nodeType&&module,Mt=Bt&&Bt.exports===_t,Ut=Mt?ct.Buffer:void 0,zt=Ut?Ut.isBuffer:void 0,Vt=zt||g,Pt=9007199254740991,qt=/^(?:0|[1-9]\d*)$/,Dt="[object Arguments]",Rt="[object Array]",Ct="[object Boolean]",$t="[object Date]",Wt="[object Error]",Nt="[object Function]",Qt="[object Map]",Gt="[object Number]",Ht="[object Object]",Jt="[object RegExp]",Kt="[object Set]",Xt="[object String]",Yt="[object WeakMap]",Zt="[object ArrayBuffer]",ne="[object DataView]",te="[object Float32Array]",ee="[object Float64Array]",re="[object Int8Array]",ue="[object Int16Array]",oe="[object Int32Array]",ie="[object Uint8Array]",ce="[object Uint8ClampedArray]",fe="[object Uint16Array]",ae="[object Uint32Array]",le={};le[te]=le[ee]=le[re]=le[ue]=le[oe]=le[ie]=le[ce]=le[fe]=le[ae]=!0,le[Dt]=le[Rt]=le[Zt]=le[Ct]=le[ne]=le[$t]=le[Wt]=le[Nt]=le[Qt]=le[Gt]=le[Ht]=le[Jt]=le[Kt]=le[Xt]=le[Yt]=!1;var se,pe="object"==typeof n&&n&&!n.nodeType&&n,he=pe&&"object"==typeof module&&module&&!module.nodeType&&module,ye=he&&he.exports===pe,ve=ye&&ot.process,de=function(){try{return ve&&ve.binding&&ve.binding("util")}catch(n){}}(),me=de&&de.isTypedArray,ge=me?S(me):j,be=Object.prototype,je=be.hasOwnProperty,Se=Object.prototype,ke=O(Object.keys,Object),we=Object.prototype,Oe=we.hasOwnProperty,xe=M(B,1/0),Ee=function(n,t,e){var r=p(n)?U:xe;r(n,t,e)},Le=z(V),Ae=o(Le),Te=P(V),Fe=M(Te,1),Ie=o(Fe),_e=u(function(n,t){return u(function(e){return n.apply(null,t.concat(e))})}),Be=R(),Me=function(n,t,e){function r(n,t){b.push(function(){f(n,t)})}function o(){if(0===b.length&&0===d)return e(null,v);for(;b.length&&d1?o(v,r):o(r)}}function a(){for(var n,t=0;j.length;)n=j.pop(),t++,D(l(n),function(n){0===--S[n]&&j.push(n)});if(t!==p)throw new Error("async.auto cannot execute tasks due to a recursive dependency")}function l(t){var e=[];return C(n,function(n,r){It(n)&&Q(n,t,0)>=0&&e.push(r)}),e}"function"==typeof t&&(e=t,t=null),e=y(e||h);var s=E(n),p=s.length;if(!p)return e(null);t||(t=p);var v={},d=0,m=!1,g=Object.create(null),b=[],j=[],S={};C(n,function(t,e){if(!It(t))return r(e,[t]),void j.push(e);var u=t.slice(0,t.length-1),o=u.length;return 0===o?(r(e,t),void j.push(e)):(S[e]=o,void D(u,function(c){if(!n[c])throw new Error("async.auto task `"+e+"` has a non-existent dependency `"+c+"` in "+u.join(", "));i(c,function(){o--,0===o&&r(e,t)})}))}),a(),o()},Ue="[object Symbol]",ze=1/0,Ve=ft?ft.prototype:void 0,Pe=Ve?Ve.toString:void 0,qe="\\ud800-\\udfff",De="\\u0300-\\u036f",Re="\\ufe20-\\ufe2f",Ce="\\u20d0-\\u20ff",$e=De+Re+Ce,We="\\ufe0e\\ufe0f",Ne="\\u200d",Qe=RegExp("["+Ne+qe+$e+We+"]"),Ge="\\ud800-\\udfff",He="\\u0300-\\u036f",Je="\\ufe20-\\ufe2f",Ke="\\u20d0-\\u20ff",Xe=He+Je+Ke,Ye="\\ufe0e\\ufe0f",Ze="["+Ge+"]",nr="["+Xe+"]",tr="\\ud83c[\\udffb-\\udfff]",er="(?:"+nr+"|"+tr+")",rr="[^"+Ge+"]",ur="(?:\\ud83c[\\udde6-\\uddff]){2}",or="[\\ud800-\\udbff][\\udc00-\\udfff]",ir="\\u200d",cr=er+"?",fr="["+Ye+"]?",ar="(?:"+ir+"(?:"+[rr,ur,or].join("|")+")"+fr+cr+")*",lr=fr+cr+ar,sr="(?:"+[rr+nr+"?",nr,ur,or,Ze].join("|")+")",pr=RegExp(tr+"(?="+tr+")|"+sr+lr,"g"),hr=/^\s+|\s+$/g,yr=/^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m,vr=/,/,dr=/(=.+)?(\s*)$/,mr=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm,gr="function"==typeof setImmediate&&setImmediate,br="object"==typeof process&&"function"==typeof process.nextTick;se=gr?setImmediate:br?process.nextTick:an;var jr=ln(se);sn.prototype.removeLink=function(n){return n.prev?n.prev.next=n.next:this.head=n.next,n.next?n.next.prev=n.prev:this.tail=n.prev,n.prev=n.next=null,this.length-=1,n},sn.prototype.empty=sn,sn.prototype.insertAfter=function(n,t){t.prev=n,t.next=n.next,n.next?n.next.prev=t:this.tail=t,n.next=t,this.length+=1},sn.prototype.insertBefore=function(n,t){t.prev=n.prev,t.next=n,n.prev?n.prev.next=t:this.head=t,n.prev=t,this.length+=1},sn.prototype.unshift=function(n){this.head?this.insertBefore(this.head,n):pn(this,n)},sn.prototype.push=function(n){this.tail?this.insertAfter(this.tail,n):pn(this,n)},sn.prototype.shift=function(){return this.head&&this.removeLink(this.head)},sn.prototype.pop=function(){return this.tail&&this.removeLink(this.tail)};var Sr,kr=M(B,1),wr=u(function(n){return u(function(t){var e=this,r=t[t.length-1];"function"==typeof r?t.pop():r=h,vn(n,t,function(n,t,r){t.apply(e,n.concat(u(function(n,t){r(n,t)})))},function(n,t){r.apply(e,[n].concat(t))})})}),Or=u(function(n){return wr.apply(null,n.reverse())}),xr=z(dn),Er=mn(dn),Lr=u(function(n){var t=[null].concat(n);return ut(function(n,e){return e.apply(this,t)})}),Ar=z(gn(r,bn)),Tr=P(gn(r,bn)),Fr=M(Tr,1),Ir=jn("dir"),_r=M(Ln,1),Br=z(gn(Tn,Tn)),Mr=P(gn(Tn,Tn)),Ur=M(Mr,1),zr=z(Bn),Vr=P(Bn),Pr=M(Vr,1),qr=jn("log"),Dr=M(Un,1/0),Rr=M(Un,1);Sr=br?process.nextTick:gr?setImmediate:an;var Cr=ln(Sr),$r=function(n,t){return hn(function(t,e){n(t[0],e)},t,1)},Wr=function(n,t){var e=$r(n,t);return e.push=function(n,t,r){if(null==r&&(r=h),"function"!=typeof r)throw new Error("task callback must be a function");if(e.started=!0,It(n)||(n=[n]),0===n.length)return jr(function(){e.drain()});t=t||0;for(var u=e._tasks.head;u&&t>=u.priority;)u=u.next;for(var o=0,i=n.length;o-1&&e%1==0&&e<=MAX_SAFE_INTEGER}function isArrayLike(e){return null!=e&&isLength(e.length)&&!isFunction(e)}function noop(){}function once(e){return function(){if(null!==e){var t=e;e=null,t.apply(this,arguments)}}}function baseTimes(e,t){for(var r=-1,n=Array(e);++r-1&&e%1==0&&eo?0:o+t),r=r>o?o:r,r<0&&(r+=o),o=t>r?0:r-t>>>0,t>>>=0;for(var i=Array(o);++n=n?e:baseSlice(e,t,r)}function charsEndIndex(e,t){for(var r=e.length;r--&&baseIndexOf(t,e[r],0)>-1;);return r}function charsStartIndex(e,t){for(var r=-1,n=e.length;++r-1;);return r}function asciiToArray(e){return e.split("")}function hasUnicode(e){return reHasUnicode.test(e)}function unicodeToArray(e){return e.match(reUnicode)||[]}function stringToArray(e){return hasUnicode(e)?unicodeToArray(e):asciiToArray(e)}function toString(e){return null==e?"":baseToString(e)}function trim(e,t,r){if(e=toString(e),e&&(r||void 0===t))return e.replace(reTrim,"");if(!e||!(t=baseToString(t)))return e;var n=stringToArray(e),o=stringToArray(t),i=charsStartIndex(n,o),a=charsEndIndex(n,o)+1;return castSlice(n,i,a).join("")}function parseParams(e){return e=e.toString().replace(STRIP_COMMENTS,""),e=e.match(FN_ARGS)[2].replace(" ",""),e=e?e.split(FN_ARG_SPLIT):[],e=e.map(function(e){return trim(e.replace(FN_ARG,""))})}function autoInject(e,t){var r={};baseForOwn(e,function(e,t){function n(t,r){var n=arrayMap(o,function(e){return t[e]});n.push(r),e.apply(null,n)}var o;if(isArray(e))o=e.slice(0,-1),e=e[e.length-1],r[t]=o.concat(o.length>0?n:e);else if(1===e.length)r[t]=e;else{if(o=parseParams(e),0===e.length&&0===o.length)throw new Error("autoInject task functions require explicit parameters.");o.pop(),r[t]=o.concat(n)}}),auto(r,t)}function fallback(e){setTimeout(e,0)}function wrap(e){return rest(function(t,r){e(function(){t.apply(null,r)})})}function DLL(){this.head=this.tail=null,this.length=0}function setInitial(e,t){e.length=1,e.head=e.tail=t}function queue(e,t,r){function n(e,t,r){if(null!=r&&"function"!=typeof r)throw new Error("task callback must be a function");if(c.started=!0,isArray(e)||(e=[e]),0===e.length&&c.idle())return setImmediate$1(function(){c.drain()});for(var n=0,o=e.length;n=0&&a.splice(s),o.callback.apply(o,t),null!=t[0]&&c.error(t[0],o.data)}i<=c.concurrency-c.buffer&&c.unsaturated(),c.idle()&&c.drain(),c.process()})}if(null==t)t=1;else if(0===t)throw new Error("Concurrency must not be zero");var i=0,a=[],s=!1,c={_tasks:new DLL,concurrency:t,payload:r,saturated:noop,unsaturated:noop,buffer:t/4,empty:noop,drain:noop,error:noop,started:!1,paused:!1,push:function(e,t){n(e,!1,t)},kill:function(){c.drain=noop,c._tasks.empty()},unshift:function(e,t){n(e,!0,t)},process:function(){if(!s){for(s=!0;!c.paused&&i1&&(n=t),r(null,{value:n})}})),e.apply(this,t)})}function reject$1(e,t,r,n){_filter(e,t,function(e,t){r(e,function(e,r){t(e,!r)})},n)}function reflectAll(e){var t;return isArray(e)?t=arrayMap(e,reflect):(t={},baseForOwn(e,function(e,r){t[r]=reflect.call(this,e)})),t}function constant$1(e){return function(){return e}}function retry(e,t,r){function n(e,t){if("object"==typeof t)e.times=+t.times||i,e.intervalFunc="function"==typeof t.interval?t.interval:constant$1(+t.interval||a),e.errorFilter=t.errorFilter;else{if("number"!=typeof t&&"string"!=typeof t)throw new Error("Invalid arguments for async.retry");e.times=+t||i}}function o(){t(function(e){e&&c++n?1:0}map(e,function(e,r){t(e,function(t,n){return t?r(t):void r(null,{value:e,criteria:n})})},function(e,t){return e?r(e):void r(null,arrayMap(t.sort(n),baseProperty("value")))})}function timeout(e,t,r){function n(){s||(i.apply(null,arguments),clearTimeout(a))}function o(){var t=e.name||"anonymous",n=new Error('Callback function "'+t+'" timed out.');n.code="ETIMEDOUT",r&&(n.info=r),s=!0,i(n)}var i,a,s=!1;return initialParams(function(r,s){i=s,a=setTimeout(o,t),e.apply(null,r.concat(n))})}function baseRange(e,t,r,n){for(var o=-1,i=nativeMax$1(nativeCeil((t-e)/(r||1)),0),a=Array(i);i--;)a[n?i:++o]=e,e+=r;return a}function timeLimit(e,t,r,n){mapLimit(baseRange(0,e,1),t,r,n)}function transform(e,t,r,n){arguments.length<=3&&(n=r,r=t,t=isArray(e)?[]:{}),n=once(n||noop),eachOf(e,function(e,n,o){r(t,e,n,o)},function(e){n(e,t)})}function unmemoize(e){return function(){return(e.unmemoized||e).apply(null,arguments)}}function whilst(e,t,r){if(r=onlyOnce(r||noop),!e())return r(null);var n=rest(function(o,i){return o?r(o):e()?t(n):void r.apply(null,[null].concat(i))});t(n)}function until(e,t,r){whilst(function(){return!e.apply(this,arguments)},t,r)}var nativeMax=Math.max,initialParams=function(e){return rest(function(t){var r=t.pop();e.call(this,t,r)})},freeGlobal="object"==typeof global&&global&&global.Object===Object&&global,freeSelf="object"==typeof self&&self&&self.Object===Object&&self,root=freeGlobal||freeSelf||Function("return this")(),Symbol$1=root.Symbol,objectProto=Object.prototype,hasOwnProperty=objectProto.hasOwnProperty,nativeObjectToString=objectProto.toString,symToStringTag$1=Symbol$1?Symbol$1.toStringTag:void 0,objectProto$1=Object.prototype,nativeObjectToString$1=objectProto$1.toString,nullTag="[object Null]",undefinedTag="[object Undefined]",symToStringTag=Symbol$1?Symbol$1.toStringTag:void 0,asyncTag="[object AsyncFunction]",funcTag="[object Function]",genTag="[object GeneratorFunction]",proxyTag="[object Proxy]",MAX_SAFE_INTEGER=9007199254740991,breakLoop={},iteratorSymbol="function"==typeof Symbol&&Symbol.iterator,getIterator=function(e){return iteratorSymbol&&e[iteratorSymbol]&&e[iteratorSymbol]()},argsTag="[object Arguments]",objectProto$3=Object.prototype,hasOwnProperty$2=objectProto$3.hasOwnProperty,propertyIsEnumerable=objectProto$3.propertyIsEnumerable,isArguments=baseIsArguments(function(){return arguments}())?baseIsArguments:function(e){return isObjectLike(e)&&hasOwnProperty$2.call(e,"callee")&&!propertyIsEnumerable.call(e,"callee")},isArray=Array.isArray,freeExports="object"==typeof exports&&exports&&!exports.nodeType&&exports,freeModule=freeExports&&"object"==typeof module&&module&&!module.nodeType&&module,moduleExports=freeModule&&freeModule.exports===freeExports,Buffer=moduleExports?root.Buffer:void 0,nativeIsBuffer=Buffer?Buffer.isBuffer:void 0,isBuffer=nativeIsBuffer||stubFalse,MAX_SAFE_INTEGER$1=9007199254740991,reIsUint=/^(?:0|[1-9]\d*)$/,argsTag$1="[object Arguments]",arrayTag="[object Array]",boolTag="[object Boolean]",dateTag="[object Date]",errorTag="[object Error]",funcTag$1="[object Function]",mapTag="[object Map]",numberTag="[object Number]",objectTag="[object Object]",regexpTag="[object RegExp]",setTag="[object Set]",stringTag="[object String]",weakMapTag="[object WeakMap]",arrayBufferTag="[object ArrayBuffer]",dataViewTag="[object DataView]",float32Tag="[object Float32Array]",float64Tag="[object Float64Array]",int8Tag="[object Int8Array]",int16Tag="[object Int16Array]",int32Tag="[object Int32Array]",uint8Tag="[object Uint8Array]",uint8ClampedTag="[object Uint8ClampedArray]",uint16Tag="[object Uint16Array]",uint32Tag="[object Uint32Array]",typedArrayTags={};typedArrayTags[float32Tag]=typedArrayTags[float64Tag]=typedArrayTags[int8Tag]=typedArrayTags[int16Tag]=typedArrayTags[int32Tag]=typedArrayTags[uint8Tag]=typedArrayTags[uint8ClampedTag]=typedArrayTags[uint16Tag]=typedArrayTags[uint32Tag]=!0,typedArrayTags[argsTag$1]=typedArrayTags[arrayTag]=typedArrayTags[arrayBufferTag]=typedArrayTags[boolTag]=typedArrayTags[dataViewTag]=typedArrayTags[dateTag]=typedArrayTags[errorTag]=typedArrayTags[funcTag$1]=typedArrayTags[mapTag]=typedArrayTags[numberTag]=typedArrayTags[objectTag]=typedArrayTags[regexpTag]=typedArrayTags[setTag]=typedArrayTags[stringTag]=typedArrayTags[weakMapTag]=!1;var freeExports$1="object"==typeof exports&&exports&&!exports.nodeType&&exports,freeModule$1=freeExports$1&&"object"==typeof module&&module&&!module.nodeType&&module,moduleExports$1=freeModule$1&&freeModule$1.exports===freeExports$1,freeProcess=moduleExports$1&&freeGlobal.process,nodeUtil=function(){try{return freeProcess&&freeProcess.binding("util")}catch(e){}}(),nodeIsTypedArray=nodeUtil&&nodeUtil.isTypedArray,isTypedArray=nodeIsTypedArray?baseUnary(nodeIsTypedArray):baseIsTypedArray,objectProto$2=Object.prototype,hasOwnProperty$1=objectProto$2.hasOwnProperty,objectProto$5=Object.prototype,nativeKeys=overArg(Object.keys,Object),objectProto$4=Object.prototype,hasOwnProperty$3=objectProto$4.hasOwnProperty,supportsSymbol="undefined"!=typeof Symbol,wrapAsync$1=supportsAsync()?function(e){return supportsSymbol&&isAsync(e)?asyncify(e):e}:identity,eachOfGeneric=doLimit(eachOfLimit,1/0),eachOf=function(e,t,r){var n=isArrayLike(e)?eachOfArrayLike:eachOfGeneric;n(e,wrapAsync$1(t),r)},map=doParallel(_asyncMap),applyEach=applyEach$1(map),mapLimit=doParallelLimit(_asyncMap),mapSeries=doLimit(mapLimit,1),applyEachSeries=applyEach$1(mapSeries),apply$2=rest(function(e,t){return rest(function(r){return e.apply(null,t.concat(r))})}),baseFor=createBaseFor(),auto=function(e,t,r){function n(e,t){g.push(function(){s(e,t)})}function o(){if(0===g.length&&0===y)return r(null,p);for(;g.length&&y1?o(p,n):o(n)}}function c(){for(var e,t=0;h.length;)e=h.pop(),t++,arrayEach(u(e),function(e){0===--b[e]&&h.push(e)});if(t!==f)throw new Error("async.auto cannot execute tasks due to a recursive dependency")}function u(t){var r=[];return baseForOwn(e,function(e,n){isArray(e)&&baseIndexOf(e,t,0)>=0&&r.push(n)}),r}"function"==typeof t&&(r=t,t=null),r=once(r||noop);var l=keys(e),f=l.length;if(!f)return r(null);t||(t=f);var p={},y=0,m=!1,d=Object.create(null),g=[],h=[],b={};baseForOwn(e,function(t,r){if(!isArray(t))return n(r,[t]),void h.push(r);var o=t.slice(0,t.length-1),a=o.length;return 0===a?(n(r,t),void h.push(r)):(b[r]=a,void arrayEach(o,function(s){if(!e[s])throw new Error("async.auto task `"+r+"` has a non-existent dependency `"+s+"` in "+o.join(", "));i(s,function(){a--,0===a&&n(r,t)})}))}),c(),o()},symbolTag="[object Symbol]",INFINITY=1/0,symbolProto=Symbol$1?Symbol$1.prototype:void 0,symbolToString=symbolProto?symbolProto.toString:void 0,rsAstralRange="\\ud800-\\udfff",rsComboMarksRange="\\u0300-\\u036f\\ufe20-\\ufe23",rsComboSymbolsRange="\\u20d0-\\u20f0",rsVarRange="\\ufe0e\\ufe0f",rsZWJ="\\u200d",reHasUnicode=RegExp("["+rsZWJ+rsAstralRange+rsComboMarksRange+rsComboSymbolsRange+rsVarRange+"]"),rsAstralRange$1="\\ud800-\\udfff",rsComboMarksRange$1="\\u0300-\\u036f\\ufe20-\\ufe23",rsComboSymbolsRange$1="\\u20d0-\\u20f0",rsVarRange$1="\\ufe0e\\ufe0f",rsAstral="["+rsAstralRange$1+"]",rsCombo="["+rsComboMarksRange$1+rsComboSymbolsRange$1+"]",rsFitz="\\ud83c[\\udffb-\\udfff]",rsModifier="(?:"+rsCombo+"|"+rsFitz+")",rsNonAstral="[^"+rsAstralRange$1+"]",rsRegional="(?:\\ud83c[\\udde6-\\uddff]){2}",rsSurrPair="[\\ud800-\\udbff][\\udc00-\\udfff]",rsZWJ$1="\\u200d",reOptMod=rsModifier+"?",rsOptVar="["+rsVarRange$1+"]?",rsOptJoin="(?:"+rsZWJ$1+"(?:"+[rsNonAstral,rsRegional,rsSurrPair].join("|")+")"+rsOptVar+reOptMod+")*",rsSeq=rsOptVar+reOptMod+rsOptJoin,rsSymbol="(?:"+[rsNonAstral+rsCombo+"?",rsCombo,rsRegional,rsSurrPair,rsAstral].join("|")+")",reUnicode=RegExp(rsFitz+"(?="+rsFitz+")|"+rsSymbol+rsSeq,"g"),reTrim=/^\s+|\s+$/g,FN_ARGS=/^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m,FN_ARG_SPLIT=/,/,FN_ARG=/(=.+)?(\s*)$/,STRIP_COMMENTS=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm,hasSetImmediate="function"==typeof setImmediate&&setImmediate,hasNextTick="object"==typeof process&&"function"==typeof process.nextTick,_defer;_defer=hasSetImmediate?setImmediate:hasNextTick?process.nextTick:fallback;var setImmediate$1=wrap(_defer);DLL.prototype.removeLink=function(e){return e.prev?e.prev.next=e.next:this.head=e.next,e.next?e.next.prev=e.prev:this.tail=e.prev,e.prev=e.next=null,this.length-=1,e},DLL.prototype.empty=DLL,DLL.prototype.insertAfter=function(e,t){t.prev=e,t.next=e.next,e.next?e.next.prev=t:this.tail=t,e.next=t,this.length+=1},DLL.prototype.insertBefore=function(e,t){t.prev=e.prev,t.next=e,e.prev?e.prev.next=t:this.head=t,e.prev=t,this.length+=1},DLL.prototype.unshift=function(e){this.head?this.insertBefore(this.head,e):setInitial(this,e)},DLL.prototype.push=function(e){this.tail?this.insertAfter(this.tail,e):setInitial(this,e)},DLL.prototype.shift=function(){return this.head&&this.removeLink(this.head)},DLL.prototype.pop=function(){return this.tail&&this.removeLink(this.tail)};var eachOfSeries=doLimit(eachOfLimit,1),seq$1=rest(function(e){return rest(function(t){var r=this,n=t[t.length-1];"function"==typeof n?t.pop():n=noop,reduce(e,t,function(e,t,n){t.apply(r,e.concat(rest(function(e,t){n(e,t)})))},function(e,t){n.apply(r,[e].concat(t))})})}),compose=rest(function(e){return seq$1.apply(null,e.reverse())}),concat=doParallel(concat$1),concatSeries=doSeries(concat$1),constant=rest(function(e){var t=[null].concat(e);return initialParams(function(e,r){return r.apply(this,t)})}),detect=doParallel(_createTester(identity,_findGetResult)),detectLimit=doParallelLimit(_createTester(identity,_findGetResult)),detectSeries=doLimit(detectLimit,1),dir=consoleFunc("dir"),eachSeries=doLimit(eachLimit$1,1),every=doParallel(_createTester(notId,notId)),everyLimit=doParallelLimit(_createTester(notId,notId)),everySeries=doLimit(everyLimit,1),filter=doParallel(_filter),filterLimit=doParallelLimit(_filter),filterSeries=doLimit(filterLimit,1),groupByLimit=function(e,t,r,n){n=n||noop,mapLimit(e,t,function(e,t){r(e,function(r,n){return r?t(r):t(null,{key:n,val:e})})},function(e,t){for(var r={},o=Object.prototype.hasOwnProperty,i=0;i=o.priority;)o=o.next;for(var i=0,a=e.length;i Date: Fri, 24 Mar 2017 14:17:23 -0700 Subject: [PATCH 04/16] wrapped all collections methods --- lib/groupByLimit.js | 6 +- lib/internal/doParallel.js | 3 +- lib/internal/doParallelLimit.js | 3 +- lib/internal/doSeries.js | 3 +- lib/internal/wrapAsync.js | 12 +- lib/mapValuesLimit.js | 4 +- lib/reduce.js | 4 +- lib/sortBy.js | 4 +- lib/transform.js | 4 +- mocha_test/es2017/asyncFunctions.js | 169 ++++++++++++++++++++++++++++ 10 files changed, 196 insertions(+), 16 deletions(-) diff --git a/lib/groupByLimit.js b/lib/groupByLimit.js index 9fc71a0c3..c14835191 100644 --- a/lib/groupByLimit.js +++ b/lib/groupByLimit.js @@ -1,6 +1,6 @@ import noop from 'lodash/noop'; import mapLimit from './mapLimit'; - +import wrapAsync from './internal/wrapAsync'; /** * The same as [`groupBy`]{@link module:Collections.groupBy} but runs a maximum of `limit` async operations at a time. * @@ -22,9 +22,9 @@ import mapLimit from './mapLimit'; */ export default function(coll, limit, iteratee, callback) { callback = callback || noop; - + var _iteratee = wrapAsync(iteratee); mapLimit(coll, limit, function(val, callback) { - iteratee(val, function(err, key) { + _iteratee(val, function(err, key) { if (err) return callback(err); return callback(null, {key: key, val: val}); }); diff --git a/lib/internal/doParallel.js b/lib/internal/doParallel.js index d43620881..deed91208 100644 --- a/lib/internal/doParallel.js +++ b/lib/internal/doParallel.js @@ -1,7 +1,8 @@ import eachOf from '../eachOf'; +import wrapAsync from './wrapAsync'; export default function doParallel(fn) { return function (obj, iteratee, callback) { - return fn(eachOf, obj, iteratee, callback); + return fn(eachOf, obj, wrapAsync(iteratee), callback); }; } diff --git a/lib/internal/doParallelLimit.js b/lib/internal/doParallelLimit.js index b29da51cd..6d0f60741 100644 --- a/lib/internal/doParallelLimit.js +++ b/lib/internal/doParallelLimit.js @@ -1,7 +1,8 @@ import eachOfLimit from './eachOfLimit'; +import wrapAsync from './wrapAsync'; export default function doParallelLimit(fn) { return function (obj, limit, iteratee, callback) { - return fn(eachOfLimit(limit), obj, iteratee, callback); + return fn(eachOfLimit(limit), obj, wrapAsync(iteratee), callback); }; } diff --git a/lib/internal/doSeries.js b/lib/internal/doSeries.js index 96e21ca65..9fea488d9 100644 --- a/lib/internal/doSeries.js +++ b/lib/internal/doSeries.js @@ -1,7 +1,8 @@ import eachOfSeries from '../eachOfSeries'; +import wrapAsync from './wrapAsync'; export default function doSeries(fn) { return function (obj, iteratee, callback) { - return fn(eachOfSeries, obj, iteratee, callback); + return fn(eachOfSeries, obj, wrapAsync(iteratee), callback); }; } diff --git a/lib/internal/wrapAsync.js b/lib/internal/wrapAsync.js index bac63a149..2cfafd4bf 100644 --- a/lib/internal/wrapAsync.js +++ b/lib/internal/wrapAsync.js @@ -1,7 +1,7 @@ import identity from 'lodash/identity'; import asyncify from '../asyncify'; -var supportsSymbol = typeof Symbol !== 'undefined'; +var supportsSymbol = typeof Symbol === 'function'; function supportsAsync() { var supported; @@ -19,10 +19,10 @@ function isAsync(fn) { return fn[Symbol.toStringTag] === 'AsyncFunction'; } -export default supportsAsync() ? - function wrapAsync(asyncFn) { - return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn; - } : - identity; +function wrapAsync(asyncFn) { + return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn; +} + +export default supportsAsync() ? wrapAsync : identity; export { supportsAsync }; diff --git a/lib/mapValuesLimit.js b/lib/mapValuesLimit.js index 6c9fe06be..1d5a47a85 100644 --- a/lib/mapValuesLimit.js +++ b/lib/mapValuesLimit.js @@ -2,6 +2,7 @@ import eachOfLimit from './eachOfLimit'; import noop from 'lodash/noop'; import once from './internal/once'; +import wrapAsync from './internal/wrapAsync'; /** * The same as [`mapValues`]{@link module:Collections.mapValues} but runs a maximum of `limit` async operations at a @@ -27,8 +28,9 @@ import once from './internal/once'; export default function mapValuesLimit(obj, limit, iteratee, callback) { callback = once(callback || noop); var newObj = {}; + var _iteratee = wrapAsync(iteratee) eachOfLimit(obj, limit, function(val, key, next) { - iteratee(val, key, function (err, result) { + _iteratee(val, key, function (err, result) { if (err) return next(err); newObj[key] = result; next(); diff --git a/lib/reduce.js b/lib/reduce.js index d2fe1c2fc..c5ac2921e 100644 --- a/lib/reduce.js +++ b/lib/reduce.js @@ -1,6 +1,7 @@ import eachOfSeries from './eachOfSeries'; import noop from 'lodash/noop'; import once from './internal/once'; +import wrapAsync from './internal/wrapAsync'; /** * Reduces `coll` into a single value using an async `iteratee` to return each @@ -44,8 +45,9 @@ import once from './internal/once'; */ export default function reduce(coll, memo, iteratee, callback) { callback = once(callback || noop); + var _iteratee = wrapAsync(iteratee); eachOfSeries(coll, function(x, i, callback) { - iteratee(memo, x, function(err, v) { + _iteratee(memo, x, function(err, v) { memo = v; callback(err); }); diff --git a/lib/sortBy.js b/lib/sortBy.js index c82920027..812a39ef3 100644 --- a/lib/sortBy.js +++ b/lib/sortBy.js @@ -2,6 +2,7 @@ import arrayMap from 'lodash/_arrayMap'; import property from 'lodash/_baseProperty'; import map from './map'; +import wrapAsync from './internal/wrapAsync'; /** * Sorts a list by the results of running each `coll` value through an async @@ -50,8 +51,9 @@ import map from './map'; * }); */ export default function sortBy (coll, iteratee, callback) { + var _iteratee = wrapAsync(iteratee); map(coll, function (x, callback) { - iteratee(x, function (err, criteria) { + _iteratee(x, function (err, criteria) { if (err) return callback(err); callback(null, {value: x, criteria: criteria}); }); diff --git a/lib/transform.js b/lib/transform.js index f001c5192..d3c1d6a03 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -3,6 +3,7 @@ import noop from 'lodash/noop'; import eachOf from './eachOf'; import once from './internal/once'; +import wrapAsync from './internal/wrapAsync'; /** * A relative of `reduce`. Takes an Object or Array, and iterates over each @@ -56,9 +57,10 @@ export default function transform (coll, accumulator, iteratee, callback) { accumulator = isArray(coll) ? [] : {}; } callback = once(callback || noop); + var _iteratee = wrapAsync(iteratee); eachOf(coll, function(v, k, cb) { - iteratee(accumulator, v, k, cb); + _iteratee(accumulator, v, k, cb); }, function(err) { callback(err, accumulator); }); diff --git a/mocha_test/es2017/asyncFunctions.js b/mocha_test/es2017/asyncFunctions.js index 46bd6ca7c..68accd88e 100644 --- a/mocha_test/es2017/asyncFunctions.js +++ b/mocha_test/es2017/asyncFunctions.js @@ -10,6 +10,7 @@ module.exports = function () { } const input = [1, 2, 3]; + const inputObj = {a: 1, b: 2, c: 3}; it('should asyncify async functions', (done) => { async.asyncify(asyncIdentity)(42, (err, val) => { @@ -18,6 +19,15 @@ module.exports = function () { }) }); + it('should handle errors in async functions', (done) => { + async.asyncify(async function () { + throw new Error('thrown error') + })((err) => { + assert(err.message = 'thrown error'); + done(); + }) + }); + it('should handle async functions in each', (done) => { async.each(input, asyncIdentity, done); }); @@ -63,6 +73,28 @@ module.exports = function () { }); }); + + it('should handle async functions in mapValues', (done) => { + async.mapValues(inputObj, asyncIdentity, (err, result) => { + expect(result).to.eql(inputObj); + done(err); + }); + }); + + it('should handle async functions in mapValuesLimit', (done) => { + async.mapValuesLimit(inputObj, 2, asyncIdentity, (err, result) => { + expect(result).to.eql(inputObj); + done(err); + }); + }); + + it('should handle async functions in mapValuesSeries', (done) => { + async.mapValuesSeries(inputObj, asyncIdentity, (err, result) => { + expect(result).to.eql(inputObj); + done(err); + }); + }); + it('should handle async functions in filter', (done) => { async.filter(input, asyncIdentity, (err, result) => { expect(result).to.eql(input); @@ -83,4 +115,141 @@ module.exports = function () { done(err); }); }); + + it('should handle async functions in reject', (done) => { + async.reject(input, asyncIdentity, (err, result) => { + expect(result).to.eql([]); + done(err); + }); + }); + + it('should handle async functions in rejectLimit', (done) => { + async.rejectLimit(input, 2, asyncIdentity, (err, result) => { + expect(result).to.eql([]); + done(err); + }); + }); + + it('should handle async functions in rejectSeries', (done) => { + async.rejectSeries(input, asyncIdentity, (err, result) => { + expect(result).to.eql([]); + done(err); + }); + }); + + it('should handle async functions in every', (done) => { + async.every(input, asyncIdentity, (err, result) => { + expect(result).to.eql(true); + done(err); + }); + }); + + it('should handle async functions in everyLimit', (done) => { + async.everyLimit(input, 2, asyncIdentity, (err, result) => { + expect(result).to.eql(true); + done(err); + }); + }); + + it('should handle async functions in everySeries', (done) => { + async.everySeries(input, asyncIdentity, (err, result) => { + expect(result).to.eql(true); + done(err); + }); + }); + + it('should handle async functions in some', (done) => { + async.some(input, asyncIdentity, (err, result) => { + expect(result).to.eql(true); + done(err); + }); + }); + + it('should handle async functions in someLimit', (done) => { + async.someLimit(input, 2, asyncIdentity, (err, result) => { + expect(result).to.eql(true); + done(err); + }); + }); + + it('should handle async functions in someSeries', (done) => { + async.someSeries(input, asyncIdentity, (err, result) => { + expect(result).to.eql(true); + done(err); + }); + }); + + it('should handle async functions in groupBy', (done) => { + async.groupBy(input, asyncIdentity, (err, result) => { + expect(result).to.eql({1: [1], 2: [2], 3: [3]}); + done(err); + }); + }); + + it('should handle async functions in groupByLimit', (done) => { + async.groupByLimit(input, 2, asyncIdentity, (err, result) => { + expect(result).to.eql({1: [1], 2: [2], 3: [3]}); + done(err); + }); + }); + + it('should handle async functions in groupBySeries', (done) => { + async.groupBySeries(input, asyncIdentity, (err, result) => { + expect(result).to.eql({1: [1], 2: [2], 3: [3]}); + done(err); + }); + }); + + + it('should handle async functions in concat', (done) => { + async.concat(input, asyncIdentity, (err, result) => { + expect(result).to.eql(input); + done(err); + }); + }); + + it('should handle async functions in concatSeries', (done) => { + async.concatSeries(input, asyncIdentity, (err, result) => { + expect(result).to.eql(input); + done(err); + }); + }); + + it('should handle async functions in reduce', (done) => { + async.reduce(input, 0, async function (acc, val) { + var res = await Promise.resolve(acc + val); + return res; + }, + (err, result) => { + expect(result).to.eql(6); + done(err); + }); + }); + + it('should handle async functions in reduceRight', (done) => { + async.reduceRight(input, 0, async function (acc, val) { + var res = await Promise.resolve(acc + val); + return res; + }, + (err, result) => { + expect(result).to.eql(6); + done(err); + }); + }); + + it('should handle async functions in sortBy', (done) => { + async.sortBy([3, 2, 1], asyncIdentity, (err, result) => { + expect(result).to.eql(input); + done(err); + }); + }); + + it('should handle async functions in transform', (done) => { + async.transform(inputObj, async function (obj, val, key) { + obj[key] = await Promise.resolve(val); + }, (err, result) => { + expect(result).to.eql(inputObj); + done(err); + }); + }); } From db9face308f5d00ca98c8d3c2080949b1db50ab7 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Sat, 25 Mar 2017 15:26:14 -0700 Subject: [PATCH 05/16] handle async funcs in control flow methods --- lib/auto.js | 3 +- lib/autoInject.js | 15 +- lib/doDuring.js | 7 +- lib/doWhilst.js | 8 +- lib/during.js | 9 +- lib/forever.js | 3 +- lib/internal/applyEach.js | 4 +- lib/internal/parallel.js | 3 +- lib/internal/queue.js | 23 +- lib/internal/wrapAsync.js | 7 +- lib/queue.js | 4 +- lib/race.js | 3 +- lib/retry.js | 5 +- lib/retryable.js | 4 +- lib/seq.js | 5 +- lib/timesLimit.js | 4 +- lib/waterfall.js | 3 +- lib/whilst.js | 6 +- mocha_test/es2017/asyncFunctions.js | 342 ++++++++++++++++++++++++++++ 19 files changed, 418 insertions(+), 40 deletions(-) diff --git a/lib/auto.js b/lib/auto.js index fe98e0659..4d7bf32d3 100644 --- a/lib/auto.js +++ b/lib/auto.js @@ -8,6 +8,7 @@ import rest from './internal/rest'; import once from './internal/once'; import onlyOnce from './internal/onlyOnce'; +import wrapAsync from './internal/wrapAsync'; /** * Determines the best order for running the functions in `tasks`, based on @@ -213,7 +214,7 @@ export default function (tasks, concurrency, callback) { })); runningTasks++; - var taskFn = task[task.length - 1]; + var taskFn = wrapAsync(task[task.length - 1]); if (task.length > 1) { taskFn(results, taskCallback); } else { diff --git a/lib/autoInject.js b/lib/autoInject.js index b0f573561..9efd2950f 100644 --- a/lib/autoInject.js +++ b/lib/autoInject.js @@ -3,8 +3,10 @@ import forOwn from 'lodash/_baseForOwn'; import arrayMap from 'lodash/_arrayMap'; import isArray from 'lodash/isArray'; import trim from 'lodash/trim'; +import wrapAsync from './internal/wrapAsync'; +import { isAsync } from './internal/wrapAsync'; -var FN_ARGS = /^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m; +var FN_ARGS = /^(?:async\s+)?(function)?\s*[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; var FN_ARG = /(=.+)?(\s*)$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; @@ -106,22 +108,25 @@ export default function autoInject(tasks, callback) { forOwn(tasks, function (taskFn, key) { var params; + var fnIsAsync = isAsync(taskFn); if (isArray(taskFn)) { params = taskFn.slice(0, -1); taskFn = taskFn[taskFn.length - 1]; newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn); - } else if (taskFn.length === 1) { + } else if ((!fnIsAsync && taskFn.length === 1) || + (fnIsAsync && taskFn.length === 0)) { // no dependencies, use the function as-is newTasks[key] = taskFn; } else { params = parseParams(taskFn); - if (taskFn.length === 0 && params.length === 0) { + if (taskFn.length === 0 && !fnIsAsync && params.length === 0) { throw new Error("autoInject task functions require explicit parameters."); } - params.pop(); + // remove callback param + if (!fnIsAsync) params.pop(); newTasks[key] = params.concat(newTask); } @@ -131,7 +136,7 @@ export default function autoInject(tasks, callback) { return results[name]; }); newArgs.push(taskCb); - taskFn.apply(null, newArgs); + wrapAsync(taskFn).apply(null, newArgs); } }); diff --git a/lib/doDuring.js b/lib/doDuring.js index b198d1720..e79574b55 100644 --- a/lib/doDuring.js +++ b/lib/doDuring.js @@ -1,6 +1,7 @@ import noop from 'lodash/noop'; import rest from './internal/rest'; import onlyOnce from './internal/onlyOnce'; +import wrapAsync from './internal/wrapAsync'; /** * The post-check version of [`during`]{@link module:ControlFlow.during}. To reflect the difference in @@ -25,17 +26,19 @@ import onlyOnce from './internal/onlyOnce'; */ export default function doDuring(fn, test, callback) { callback = onlyOnce(callback || noop); + var _fn = wrapAsync(fn); + var _test = wrapAsync(test); var next = rest(function(err, args) { if (err) return callback(err); args.push(check); - test.apply(this, args); + _test.apply(this, args); }); function check(err, truth) { if (err) return callback(err); if (!truth) return callback(null); - fn(next); + _fn(next); } check(null, true); diff --git a/lib/doWhilst.js b/lib/doWhilst.js index d9222aa3d..39f460008 100644 --- a/lib/doWhilst.js +++ b/lib/doWhilst.js @@ -2,6 +2,7 @@ import noop from 'lodash/noop'; import rest from './internal/rest'; import onlyOnce from './internal/onlyOnce'; +import wrapAsync from './internal/wrapAsync'; /** * The post-check version of [`whilst`]{@link module:ControlFlow.whilst}. To reflect the difference in @@ -19,7 +20,7 @@ import onlyOnce from './internal/onlyOnce'; * passes. The function is passed a `callback(err)`, which must be called once * it has completed with an optional `err` argument. Invoked with (callback). * @param {Function} test - synchronous truth test to perform after each - * execution of `iteratee`. Invoked with the non-error callback results of + * execution of `iteratee`. Invoked with the non-error callback results of * `iteratee`. * @param {Function} [callback] - A callback which is called after the test * function has failed and repeated execution of `iteratee` has stopped. @@ -28,10 +29,11 @@ import onlyOnce from './internal/onlyOnce'; */ export default function doWhilst(iteratee, test, callback) { callback = onlyOnce(callback || noop); + var _iteratee = wrapAsync(iteratee); var next = rest(function(err, args) { if (err) return callback(err); - if (test.apply(this, args)) return iteratee(next); + if (test.apply(this, args)) return _iteratee(next); callback.apply(null, [null].concat(args)); }); - iteratee(next); + _iteratee(next); } diff --git a/lib/during.js b/lib/during.js index 549ad445c..dd9ba997a 100644 --- a/lib/during.js +++ b/lib/during.js @@ -1,5 +1,6 @@ import noop from 'lodash/noop'; import onlyOnce from './internal/onlyOnce'; +import wrapAsync from './internal/wrapAsync'; /** * Like [`whilst`]{@link module:ControlFlow.whilst}, except the `test` is an asynchronous function that @@ -40,17 +41,19 @@ import onlyOnce from './internal/onlyOnce'; */ export default function during(test, fn, callback) { callback = onlyOnce(callback || noop); + var _fn = wrapAsync(fn); + var _test = wrapAsync(test); function next(err) { if (err) return callback(err); - test(check); + _test(check); } function check(err, truth) { if (err) return callback(err); if (!truth) return callback(null); - fn(next); + _fn(next); } - test(check); + _test(check); } diff --git a/lib/forever.js b/lib/forever.js index 01473953c..d1f3256c6 100644 --- a/lib/forever.js +++ b/lib/forever.js @@ -2,6 +2,7 @@ import noop from 'lodash/noop'; import onlyOnce from './internal/onlyOnce'; import ensureAsync from './ensureAsync'; +import wrapAsync from './internal/wrapAsync'; /** * Calls the asynchronous function `fn` with a callback parameter that allows it @@ -34,7 +35,7 @@ import ensureAsync from './ensureAsync'; */ export default function forever(fn, errback) { var done = onlyOnce(errback || noop); - var task = ensureAsync(fn); + var task = ensureAsync(wrapAsync(fn)); function next(err) { if (err) return done(err); diff --git a/lib/internal/applyEach.js b/lib/internal/applyEach.js index aca2b7d5b..f611def88 100644 --- a/lib/internal/applyEach.js +++ b/lib/internal/applyEach.js @@ -1,11 +1,13 @@ +import arrayMap from 'lodash/_arrayMap' import rest from './rest'; import initialParams from './initialParams'; +import wrapAsync from './wrapAsync'; export default function applyEach(eachfn) { return rest(function(fns, args) { var go = initialParams(function(args, callback) { var that = this; - return eachfn(fns, function (fn, cb) { + return eachfn(arrayMap(fns, wrapAsync), function (fn, cb) { fn.apply(that, args.concat(cb)); }, callback); }); diff --git a/lib/internal/parallel.js b/lib/internal/parallel.js index d7c5cd902..70993d3dd 100644 --- a/lib/internal/parallel.js +++ b/lib/internal/parallel.js @@ -1,13 +1,14 @@ import noop from 'lodash/noop'; import isArrayLike from 'lodash/isArrayLike'; import rest from './rest'; +import wrapAsync from './wrapAsync'; export default function _parallel(eachfn, tasks, callback) { callback = callback || noop; var results = isArrayLike(tasks) ? [] : {}; eachfn(tasks, function (task, key, callback) { - task(rest(function (err, args) { + wrapAsync(task)(rest(function (err, args) { if (args.length <= 1) { args = args[0]; } diff --git a/lib/internal/queue.js b/lib/internal/queue.js index 7a438da27..c825e62d5 100644 --- a/lib/internal/queue.js +++ b/lib/internal/queue.js @@ -6,6 +6,7 @@ import rest from './rest'; import onlyOnce from './onlyOnce'; import setImmediate from './setImmediate'; import DLL from './DoublyLinkedList'; +import wrapAsync from './wrapAsync'; export default function queue(worker, concurrency, payload) { if (concurrency == null) { @@ -15,6 +16,10 @@ export default function queue(worker, concurrency, payload) { throw new Error('Concurrency must not be zero'); } + var _worker = wrapAsync(worker); + var numRunning = 0; + var workersList = []; + function _insert(data, insertAtFront, callback) { if (callback != null && typeof callback !== 'function') { throw new Error('task callback must be a function'); @@ -47,7 +52,7 @@ export default function queue(worker, concurrency, payload) { function _next(tasks) { return rest(function(args){ - workers -= 1; + numRunning -= 1; for (var i = 0, l = tasks.length; i < l; i++) { var task = tasks[i]; @@ -63,7 +68,7 @@ export default function queue(worker, concurrency, payload) { } } - if (workers <= (q.concurrency - q.buffer) ) { + if (numRunning <= (q.concurrency - q.buffer) ) { q.unsaturated(); } @@ -74,8 +79,6 @@ export default function queue(worker, concurrency, payload) { }); } - var workers = 0; - var workersList = []; var isProcessing = false; var q = { _tasks: new DLL(), @@ -106,7 +109,7 @@ export default function queue(worker, concurrency, payload) { return; } isProcessing = true; - while(!q.paused && workers < q.concurrency && q._tasks.length){ + while(!q.paused && numRunning < q.concurrency && q._tasks.length){ var tasks = [], data = []; var l = q._tasks.length; if (q.payload) l = Math.min(l, q.payload); @@ -119,15 +122,15 @@ export default function queue(worker, concurrency, payload) { if (q._tasks.length === 0) { q.empty(); } - workers += 1; + numRunning += 1; workersList.push(tasks[0]); - if (workers === q.concurrency) { + if (numRunning === q.concurrency) { q.saturated(); } var cb = onlyOnce(_next(tasks)); - worker(data, cb); + _worker(data, cb); } isProcessing = false; }, @@ -135,13 +138,13 @@ export default function queue(worker, concurrency, payload) { return q._tasks.length; }, running: function () { - return workers; + return numRunning; }, workersList: function () { return workersList; }, idle: function() { - return q._tasks.length + workers === 0; + return q._tasks.length + numRunning === 0; }, pause: function () { q.paused = true; diff --git a/lib/internal/wrapAsync.js b/lib/internal/wrapAsync.js index 2cfafd4bf..aee027504 100644 --- a/lib/internal/wrapAsync.js +++ b/lib/internal/wrapAsync.js @@ -7,8 +7,7 @@ function supportsAsync() { var supported; try { /* eslint no-eval: 0 */ - supported = supportsSymbol && - isAsync(eval('(async function () {})')); + supported = isAsync(eval('(async function () {})')); } catch (e) { supported = false; } @@ -16,7 +15,7 @@ function supportsAsync() { } function isAsync(fn) { - return fn[Symbol.toStringTag] === 'AsyncFunction'; + return supportsSymbol && fn[Symbol.toStringTag] === 'AsyncFunction'; } function wrapAsync(asyncFn) { @@ -25,4 +24,4 @@ function wrapAsync(asyncFn) { export default supportsAsync() ? wrapAsync : identity; -export { supportsAsync }; +export { supportsAsync, isAsync }; diff --git a/lib/queue.js b/lib/queue.js index 5666843a5..0f17cd996 100644 --- a/lib/queue.js +++ b/lib/queue.js @@ -1,4 +1,5 @@ import queue from './internal/queue'; +import wrapAsync from './internal/wrapAsync'; /** * A queue of tasks for the worker function to complete. @@ -101,7 +102,8 @@ import queue from './internal/queue'; * }); */ export default function (worker, concurrency) { + var _worker = wrapAsync(worker); return queue(function (items, cb) { - worker(items[0], cb); + _worker(items[0], cb); }, concurrency, 1); } diff --git a/lib/race.js b/lib/race.js index 5547c8662..ee81b37be 100644 --- a/lib/race.js +++ b/lib/race.js @@ -1,6 +1,7 @@ import isArray from 'lodash/isArray'; import noop from 'lodash/noop'; import once from './internal/once'; +import wrapAsync from './internal/wrapAsync'; /** * Runs the `tasks` array of functions in parallel, without waiting until the @@ -44,6 +45,6 @@ export default function race(tasks, callback) { if (!isArray(tasks)) return callback(new TypeError('First argument to race must be an array of functions')); if (!tasks.length) return callback(); for (var i = 0, l = tasks.length; i < l; i++) { - tasks[i](callback); + wrapAsync(tasks[i])(callback); } } diff --git a/lib/retry.js b/lib/retry.js index a5ad866bf..e2790160e 100644 --- a/lib/retry.js +++ b/lib/retry.js @@ -1,5 +1,6 @@ import noop from 'lodash/noop'; import constant from 'lodash/constant'; +import wrapAsync from './internal/wrapAsync'; /** * Attempts to get a successful response from `task` no more than `times` times @@ -124,9 +125,11 @@ export default function retry(opts, task, callback) { throw new Error("Invalid arguments for async.retry"); } + var _task = wrapAsync(task); + var attempt = 1; function retryAttempt() { - task(function(err) { + _task(function(err) { if (err && attempt++ < options.times && (typeof options.errorFilter != 'function' || options.errorFilter(err))) { diff --git a/lib/retryable.js b/lib/retryable.js index a5419779c..6fba8e234 100644 --- a/lib/retryable.js +++ b/lib/retryable.js @@ -1,5 +1,6 @@ import retry from './retry'; import initialParams from './internal/initialParams'; +import wrapAsync from './internal/wrapAsync'; /** * A close relative of [`retry`]{@link module:ControlFlow.retry}. This method wraps a task and makes it @@ -30,9 +31,10 @@ export default function (opts, task) { task = opts; opts = null; } + var _task = wrapAsync(task); return initialParams(function (args, callback) { function taskFn(cb) { - task.apply(null, args.concat(cb)); + _task.apply(null, args.concat(cb)); } if (opts) retry(opts, taskFn, callback); diff --git a/lib/seq.js b/lib/seq.js index 8bd1121cc..9a3584718 100644 --- a/lib/seq.js +++ b/lib/seq.js @@ -1,6 +1,8 @@ import noop from 'lodash/noop'; import rest from './internal/rest'; import reduce from './reduce'; +import wrapAsync from './internal/wrapAsync'; +import arrayMap from 'lodash/_arrayMap'; /** * Version of the compose function that is more natural to read. Each function @@ -41,6 +43,7 @@ import reduce from './reduce'; * }); */ export default rest(function seq(functions) { + var _functions = arrayMap(functions, wrapAsync); return rest(function(args) { var that = this; @@ -51,7 +54,7 @@ export default rest(function seq(functions) { cb = noop; } - reduce(functions, args, function(newargs, fn, cb) { + reduce(_functions, args, function(newargs, fn, cb) { fn.apply(that, newargs.concat(rest(function(err, nextargs) { cb(err, nextargs); }))); diff --git a/lib/timesLimit.js b/lib/timesLimit.js index 68d5edee4..7ba91d69c 100644 --- a/lib/timesLimit.js +++ b/lib/timesLimit.js @@ -1,5 +1,6 @@ import mapLimit from './mapLimit'; import range from 'lodash/_baseRange'; +import wrapAsync from './internal/wrapAsync'; /** * The same as [times]{@link module:ControlFlow.times} but runs a maximum of `limit` async operations at a @@ -18,5 +19,6 @@ import range from 'lodash/_baseRange'; * @param {Function} callback - see [async.map]{@link module:Collections.map}. */ export default function timeLimit(count, limit, iteratee, callback) { - mapLimit(range(0, count, 1), limit, iteratee, callback); + var _iteratee = wrapAsync(iteratee); + mapLimit(range(0, count, 1), limit, _iteratee, callback); } diff --git a/lib/waterfall.js b/lib/waterfall.js index 7bc60daa9..cb2c1fd02 100644 --- a/lib/waterfall.js +++ b/lib/waterfall.js @@ -4,6 +4,7 @@ import once from './internal/once'; import rest from './internal/rest'; import onlyOnce from './internal/onlyOnce'; +import wrapAsync from './internal/wrapAsync'; /** * Runs the `tasks` array of functions in series, each passing their results to @@ -82,7 +83,7 @@ export default function(tasks, callback) { args.push(taskCallback); - var task = tasks[taskIndex++]; + var task = wrapAsync(tasks[taskIndex++]); task.apply(null, args); } diff --git a/lib/whilst.js b/lib/whilst.js index ac2a79c03..8135af7d9 100644 --- a/lib/whilst.js +++ b/lib/whilst.js @@ -2,6 +2,7 @@ import noop from 'lodash/noop'; import rest from './internal/rest'; import onlyOnce from './internal/onlyOnce'; +import wrapAsync from './internal/wrapAsync'; /** * Repeatedly call `iteratee`, while `test` returns `true`. Calls `callback` when @@ -40,11 +41,12 @@ import onlyOnce from './internal/onlyOnce'; */ export default function whilst(test, iteratee, callback) { callback = onlyOnce(callback || noop); + var _iteratee = wrapAsync(iteratee); if (!test()) return callback(null); var next = rest(function(err, args) { if (err) return callback(err); - if (test()) return iteratee(next); + if (test()) return _iteratee(next); callback.apply(null, [null].concat(args)); }); - iteratee(next); + _iteratee(next); } diff --git a/mocha_test/es2017/asyncFunctions.js b/mocha_test/es2017/asyncFunctions.js index 68accd88e..dc22f62c1 100644 --- a/mocha_test/es2017/asyncFunctions.js +++ b/mocha_test/es2017/asyncFunctions.js @@ -28,6 +28,10 @@ module.exports = function () { }) }); + /* + * Collections + */ + it('should handle async functions in each', (done) => { async.each(input, asyncIdentity, done); }); @@ -252,4 +256,342 @@ module.exports = function () { done(err); }); }); + + /* + * Control Flow + */ + + it('should handle async functions in applyEach', (done) => { + async.applyEach([asyncIdentity, asyncIdentity])(input, (err, result) => { + expect(result).to.eql([input, input]); + done(err); + }); + }); + + it('should handle async functions in applyEachSeries', (done) => { + async.applyEachSeries([asyncIdentity, asyncIdentity])(input, (err, result) => { + expect(result).to.eql([input, input]); + done(err); + }); + }); + + it('should handle async functions in auto', (done) => { + async.auto({ + a: async function () { + return await Promise.resolve(1); + }, + b: async function () { + return await Promise.resolve(2); + }, + c: ['a', 'b', async function (results) { + return await Promise.resolve(results.a + results.b); + }] + }, (err, result) => { + expect(result).to.eql({a: 1, b: 2, c: 3}); + done(err); + }); + }); + + it('should handle async functions in autoInject', (done) => { + async.autoInject({ + a: async function () { + return await Promise.resolve(1); + }, + b: async function (a) { + return await Promise.resolve(a + 1); + }, + c: async (a, b) => { + return await Promise.resolve(a + b); + }, + d: async (c) => { + return await Promise.resolve(c + 1); + } + }, (err, result) => { + expect(result).to.eql({a: 1, b: 2, c: 3, d: 4}); + done(err); + }); + }); + + it('should handle async functions in cargo', (done) => { + var result = []; + var q = async.cargo(async function(val) { + result.push(await Promise.resolve(val)); + }, 2) + + q.drain = () => { + expect(result).to.eql([[1, 2], [3]]); + done(); + }; + + q.push(1); + q.push(2); + q.push(3); + }); + + it('should handle async functions in queue', (done) => { + var result = []; + var q = async.queue(async function(val) { + result.push(await Promise.resolve(val)); + }, 2) + + q.drain = () => { + expect(result).to.eql([1, 2, 3]); + done(); + }; + + q.push(1); + q.push(2); + q.push(3); + }); + + it('should handle async functions in priorityQueue', (done) => { + var result = []; + var q = async.priorityQueue(async function(val) { + result.push(await Promise.resolve(val)); + }, 2) + + q.drain = () => { + expect(result).to.eql([1, 2, 3]); + done(); + }; + + q.push(1); + q.push(2); + q.push(3); + }); + + it('should handle async functions in compose', (done) => { + async.compose( + async (a) => a + 1, + async (a) => a + 1, + async (a) => a + 1 + )(0, (err, result) => { + expect(result).to.equal(3); + done(err); + }); + }); + + it('should handle async functions in seq', (done) => { + async.seq( + async (a) => a + 1, + async (a) => a + 1, + async (a) => a + 1 + )(0, (err, result) => { + expect(result).to.equal(3); + done(err); + }); + }); + + it('should handle async functions in during', (done) => { + var val = 0; + async.during(async () => { + return val < 3; + }, + async () => { + val += 1; + return val; + }, done); + }); + + it('should handle async functions in doDuring', (done) => { + var val = 0; + async.doDuring(async () => { + val += 1; + return val; + }, + async (res) => { + return res < 3; + }, done); + }); + + it('should handle async functions in whilst', (done) => { + var val = 0; + async.whilst(() => val < 3, + async () => { + val += 1; + return val; + }, done); + }); + + it('should handle async functions in doWhilst', (done) => { + var val = 0; + async.doWhilst(async () => { + val += 1; + return val; + }, (res) => res < 3, done); + }); + + it('should handle async functions in until', (done) => { + var val = 0; + async.until(() => val > 3, + async () => { + val += 1; + return val; + }, done); + }); + + it('should handle async functions in doUntil', (done) => { + var val = 0; + async.doUntil(async () => { + val += 1; + return val; + }, (res) => res > 3, done); + }); + + it('should handle async functions in forever', (done) => { + var counter = 0; + async.forever(async () => { + counter += 1; + if (counter > 10) throw new Error('too big'); + },(err) => { + expect(err.message).to.equal('too big'); + done(); + }) + }); + + it('should handle async functions in parallel', (done) => { + async.parallel([ + async () => 1, + async () => 2, + async () => 3 + ], (err, result) => { + expect(result).to.eql([1, 2, 3]); + done(err); + }) + }); + + it('should handle async functions in parallel (object)', (done) => { + async.parallel({ + a: async () => 1, + b: async () => 2, + c: async () => 3 + }, (err, result) => { + expect(result).to.eql({a: 1, b: 2, c: 3}); + done(err); + }) + }); + + it('should handle async functions in parallelLimit', (done) => { + async.parallelLimit([ + async () => 1, + async () => 2, + async () => 3 + ], 2, (err, result) => { + expect(result).to.eql([1, 2, 3]); + done(err); + }) + }); + + it('should handle async functions in parallelLimit (object)', (done) => { + async.parallelLimit({ + a: async () => 1, + b: async () => 2, + c: async () => 3 + }, 2, (err, result) => { + expect(result).to.eql({a: 1, b: 2, c: 3}); + done(err); + }) + }); + + it('should handle async functions in series', (done) => { + async.series([ + async () => 1, + async () => 2, + async () => 3 + ], (err, result) => { + expect(result).to.eql([1, 2, 3]); + done(err); + }) + }); + + it('should handle async functions in series (object)', (done) => { + async.series({ + a: async () => 1, + b: async () => 2, + c: async () => 3 + }, (err, result) => { + expect(result).to.eql({a: 1, b: 2, c: 3}); + done(err); + }) + }); + + it('should handle async functions in race', (done) => { + async.race([ + async () => 1, + async () => 2, + async () => 3 + ], (err, result) => { + expect(result).to.eql(1); + done(err); + }) + }); + + it('should handle async functions in retry', (done) => { + var count = 0; + async.retry(4, async () => { + count += 1; + if (count < 3) throw new Error('fail'); + return count; + }, (err, result) => { + expect(result).to.eql(3); + done(err); + }) + }); + + it('should handle async functions in retryable', (done) => { + var count = 0; + async.retryable(4, async () => { + count += 1; + if (count < 3) throw new Error('fail'); + return count; + })((err, result) => { + expect(result).to.eql(3); + done(err); + }) + }); + + it('should handle async functions in times', (done) => { + var count = 0; + async.times(4, async () => { + count += 1; + return count; + }, (err, result) => { + expect(result).to.eql([1, 2, 3, 4]); + done(err); + }) + }); + + it('should handle async functions in timesLimit', (done) => { + var count = 0; + async.timesLimit(4, 2, async () => { + count += 1; + return count; + }, (err, result) => { + expect(result).to.eql([1, 2, 3, 4]); + done(err); + }) + }); + + it('should handle async functions in timesSeries', (done) => { + var count = 0; + async.timesSeries(4, async () => { + count += 1; + return count; + }, (err, result) => { + expect(result).to.eql([1, 2, 3, 4]); + done(err); + }) + }); + + it('should handle async functions in waterfall', (done) => { + async.waterfall([ + async () => 1, + async (a) => a + 1, + async (a) => [a, a + 1], + async ([a, b]) => a + b, + ], (err, result) => { + expect(result).to.eql(5); + done(err); + }) + }); } From 0bae58f5db90218aefcb4d7ec59d36695553b02c Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Sat, 25 Mar 2017 15:43:51 -0700 Subject: [PATCH 06/16] handle async functions in relevant utility methods --- lib/ensureAsync.js | 2 + lib/forever.js | 2 +- lib/internal/consoleFunc.js | 3 +- lib/memoize.js | 4 +- lib/reflect.js | 4 +- lib/timeout.js | 5 ++- mocha_test/es2017/asyncFunctions.js | 65 +++++++++++++++++++++++++++++ 7 files changed, 80 insertions(+), 5 deletions(-) diff --git a/lib/ensureAsync.js b/lib/ensureAsync.js index c60d07e54..8582bbabd 100644 --- a/lib/ensureAsync.js +++ b/lib/ensureAsync.js @@ -1,5 +1,6 @@ import setImmediate from './internal/setImmediate'; import initialParams from './internal/initialParams'; +import { isAsync } from './internal/wrapAsync'; /** * Wrap an async function and ensure it calls its callback on a later tick of @@ -36,6 +37,7 @@ import initialParams from './internal/initialParams'; * async.mapSeries(args, async.ensureAsync(sometimesAsync), done); */ export default function ensureAsync(fn) { + if (isAsync(fn)) return fn; return initialParams(function (args, callback) { var sync = true; args.push(function () { diff --git a/lib/forever.js b/lib/forever.js index d1f3256c6..66bc47276 100644 --- a/lib/forever.js +++ b/lib/forever.js @@ -35,7 +35,7 @@ import wrapAsync from './internal/wrapAsync'; */ export default function forever(fn, errback) { var done = onlyOnce(errback || noop); - var task = ensureAsync(wrapAsync(fn)); + var task = wrapAsync(ensureAsync(fn)); function next(err) { if (err) return done(err); diff --git a/lib/internal/consoleFunc.js b/lib/internal/consoleFunc.js index 6ab2f7bb5..35374d713 100644 --- a/lib/internal/consoleFunc.js +++ b/lib/internal/consoleFunc.js @@ -1,9 +1,10 @@ import arrayEach from 'lodash/_arrayEach'; import rest from './rest'; +import wrapAsync from './wrapAsync'; export default function consoleFunc(name) { return rest(function (fn, args) { - fn.apply(null, args.concat(rest(function (err, args) { + wrapAsync(fn).apply(null, args.concat(rest(function (err, args) { if (typeof console === 'object') { if (err) { if (console.error) { diff --git a/lib/memoize.js b/lib/memoize.js index ec836200f..d752d999a 100644 --- a/lib/memoize.js +++ b/lib/memoize.js @@ -3,6 +3,7 @@ import rest from './internal/rest'; import setImmediate from './internal/setImmediate'; import initialParams from './internal/initialParams'; +import wrapAsync from './internal/wrapAsync'; function has(obj, key) { return key in obj; @@ -49,6 +50,7 @@ export default function memoize(fn, hasher) { var memo = Object.create(null); var queues = Object.create(null); hasher = hasher || identity; + var _fn = wrapAsync(fn); var memoized = initialParams(function memoized(args, callback) { var key = hasher.apply(null, args); if (has(memo, key)) { @@ -59,7 +61,7 @@ export default function memoize(fn, hasher) { queues[key].push(callback); } else { queues[key] = [callback]; - fn.apply(null, args.concat(rest(function(args) { + _fn.apply(null, args.concat(rest(function(args) { memo[key] = args; var q = queues[key]; delete queues[key]; diff --git a/lib/reflect.js b/lib/reflect.js index cc8d6b613..0dbd397a6 100644 --- a/lib/reflect.js +++ b/lib/reflect.js @@ -1,5 +1,6 @@ import initialParams from './internal/initialParams'; import rest from './internal/rest'; +import wrapAsync from './internal/wrapAsync'; /** * Wraps the function in another function that always returns data even when it @@ -41,6 +42,7 @@ import rest from './internal/rest'; * }); */ export default function reflect(fn) { + var _fn = wrapAsync(fn); return initialParams(function reflectOn(args, reflectCallback) { args.push(rest(function callback(err, cbArgs) { if (err) { @@ -60,6 +62,6 @@ export default function reflect(fn) { } })); - return fn.apply(this, args); + return _fn.apply(this, args); }); } diff --git a/lib/timeout.js b/lib/timeout.js index 93fe2e1c5..ad050e01a 100644 --- a/lib/timeout.js +++ b/lib/timeout.js @@ -1,4 +1,5 @@ import initialParams from './internal/initialParams'; +import wrapAsync from './internal/wrapAsync'; /** * Sets a time limit on an asynchronous function. If the function does not call @@ -64,10 +65,12 @@ export default function timeout(asyncFn, milliseconds, info) { originalCallback(error); } + var fn = wrapAsync(asyncFn); + return initialParams(function (args, origCallback) { originalCallback = origCallback; // setup timer and call original function timer = setTimeout(timeoutCallback, milliseconds); - asyncFn.apply(null, args.concat(injectedCallback)); + fn.apply(null, args.concat(injectedCallback)); }); } diff --git a/mocha_test/es2017/asyncFunctions.js b/mocha_test/es2017/asyncFunctions.js index dc22f62c1..60a1af32b 100644 --- a/mocha_test/es2017/asyncFunctions.js +++ b/mocha_test/es2017/asyncFunctions.js @@ -594,4 +594,69 @@ module.exports = function () { done(err); }) }); + + /** + * Utils + */ + + it('should handle async functions in dir', (done) => { + async.dir(async (val) => val, 'foo'); + setTimeout(done); + }); + + it('should handle async functions in log', (done) => { + async.log(async (val) => val, 'foo'); + setTimeout(done); + }); + + it('should handle async functions in ensureAsync', () => { + var fn = async.ensureAsync(asyncIdentity); + assert(fn === asyncIdentity); + }); + + it('should handle async functions in memoize', (done) => { + var fn = async.memoize(asyncIdentity); + fn(1, () => { + fn(1, done); + }) + }); + + it('should handle async functions in reflect', (done) => { + var fn = async.reflect(asyncIdentity); + fn(1, (err, result) => { + expect(result).to.eql({value: 1}); + done(err); + }) + }); + + it('should handle async functions in reflect (error case)', (done) => { + var thrown; + var fn = async.reflect(async () => { + thrown = new Error('foo'); + throw thrown; + }); + fn(1, (err, result) => { + expect(result).to.eql({error: thrown}); + done(err); + }) + }); + + it('should handle async functions in timeout', (done) => { + var fn = async.timeout(asyncIdentity, 50); + fn(1, (err, result) => { + expect(result).to.eql(1); + done(err); + }) + }); + + it('should handle async functions in timeout (error case)', (done) => { + var fn = async.timeout(async (val) => { + await new Promise((resolve) => setTimeout(resolve, 100)); + return val; + }, 50); + fn(1, (err) => { + expect(err.message).to.match(/timed out/); + done(); + }) + }); } From 22a5354ce9d80dc6fac8f029ca62a729ed53de76 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Sat, 25 Mar 2017 16:00:29 -0700 Subject: [PATCH 07/16] add test case for autoInject holy grail --- mocha_test/es2017/asyncFunctions.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/mocha_test/es2017/asyncFunctions.js b/mocha_test/es2017/asyncFunctions.js index 60a1af32b..ca76a56c9 100644 --- a/mocha_test/es2017/asyncFunctions.js +++ b/mocha_test/es2017/asyncFunctions.js @@ -312,6 +312,26 @@ module.exports = function () { }); }); + it('should handle async functions in autoInject (shorthand)', (done) => { + async.autoInject({ + async a() { + return await Promise.resolve(1); + }, + async b(a) { + return await Promise.resolve(a + 1); + }, + async c(a, b) { + return await Promise.resolve(a + b); + }, + async d(c) { + return await Promise.resolve(c + 1); + } + }, (err, result) => { + expect(result).to.eql({a: 1, b: 2, c: 3, d: 4}); + done(err); + }); + }); + it('should handle async functions in cargo', (done) => { var result = []; var q = async.cargo(async function(val) { From 4b1278b2436b6a9c25d73cdd97d750f6563361d0 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Mon, 27 Mar 2017 12:25:48 -0700 Subject: [PATCH 08/16] re-enable firefox testing --- .travis.yml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 267defc44..1cd205f79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,23 +6,25 @@ node_js: - "4" - "6" - "7" - -#matrix: -# include: -# - node_js: "6" -# addons: -# firefox: "49.0" -# env: BROWSER=true MAKE_TEST=true env: matrix: BROWSER=false MAKE_TEST=false +matrix: + include: + - node_js: "7" + addons: + firefox: + - "45.0" + - "52.0" + env: BROWSER=true MAKE_TEST=true + after_success: npm run coveralls # Needed to run Karma with Firefox on Travis # http://karma-runner.github.io/0.13/plus/travis.html #before_script: -# - export DISPLAY=:99.0 -# - sh -e /etc/init.d/xvfb start + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start script: - npm test From e62c53c8ed31933b1dab7d0968998a12b1bac84d Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Mon, 27 Mar 2017 12:32:15 -0700 Subject: [PATCH 09/16] fix travis yaml --- .travis.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1cd205f79..cea414ff2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,25 +6,23 @@ node_js: - "4" - "6" - "7" -env: - matrix: BROWSER=false MAKE_TEST=false matrix: include: - node_js: "7" addons: - firefox: - - "45.0" - - "52.0" + firefox: "52.0" env: BROWSER=true MAKE_TEST=true +env: + matrix: BROWSER=false MAKE_TEST=false after_success: npm run coveralls # Needed to run Karma with Firefox on Travis # http://karma-runner.github.io/0.13/plus/travis.html #before_script: - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start +# - export DISPLAY=:99.0 +# - sh -e /etc/init.d/xvfb start script: - npm test From 700aa23851833ac9baf02f6c4fda72c00eeb224c Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Mon, 27 Mar 2017 12:42:19 -0700 Subject: [PATCH 10/16] re-enable xvfb for travis --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index cea414ff2..6732369c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,9 +20,9 @@ after_success: npm run coveralls # Needed to run Karma with Firefox on Travis # http://karma-runner.github.io/0.13/plus/travis.html -#before_script: -# - export DISPLAY=:99.0 -# - sh -e /etc/init.d/xvfb start +before_script: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start script: - npm test From 57b88774c5ed515ead36fb84f81550f7ff807001 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Mon, 27 Mar 2017 12:54:04 -0700 Subject: [PATCH 11/16] fix small issues from PR --- lib/autoInject.js | 6 ++++-- lib/internal/applyEach.js | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/autoInject.js b/lib/autoInject.js index 9efd2950f..3ba87fcc5 100644 --- a/lib/autoInject.js +++ b/lib/autoInject.js @@ -109,14 +109,16 @@ export default function autoInject(tasks, callback) { forOwn(tasks, function (taskFn, key) { var params; var fnIsAsync = isAsync(taskFn); + var hasNoDeps = + (!fnIsAsync && taskFn.length === 1) || + (fnIsAsync && taskFn.length === 0); if (isArray(taskFn)) { params = taskFn.slice(0, -1); taskFn = taskFn[taskFn.length - 1]; newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn); - } else if ((!fnIsAsync && taskFn.length === 1) || - (fnIsAsync && taskFn.length === 0)) { + } else if (hasNoDeps) { // no dependencies, use the function as-is newTasks[key] = taskFn; } else { diff --git a/lib/internal/applyEach.js b/lib/internal/applyEach.js index f611def88..285a6a25e 100644 --- a/lib/internal/applyEach.js +++ b/lib/internal/applyEach.js @@ -1,4 +1,3 @@ -import arrayMap from 'lodash/_arrayMap' import rest from './rest'; import initialParams from './initialParams'; import wrapAsync from './wrapAsync'; @@ -7,8 +6,8 @@ export default function applyEach(eachfn) { return rest(function(fns, args) { var go = initialParams(function(args, callback) { var that = this; - return eachfn(arrayMap(fns, wrapAsync), function (fn, cb) { - fn.apply(that, args.concat(cb)); + return eachfn(fns, function (fn, cb) { + wrapAsync(fn).apply(that, args.concat(cb)); }, callback); }); if (args.length) { From 6ac9cb0a26b8f026a8454d6dbc4eb15482057c83 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Mon, 27 Mar 2017 14:00:16 -0700 Subject: [PATCH 12/16] custom typedef doc test --- lib/each.js | 10 ++++------ lib/index.js | 43 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/lib/each.js b/lib/each.js index e20b96516..29f2bafc8 100644 --- a/lib/each.js +++ b/lib/each.js @@ -19,12 +19,10 @@ import wrapAsync from './internal/wrapAsync' * @alias forEach * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A function to apply to each item - * in `coll`. The iteratee is passed a `callback(err)` which must be called once - * it has completed. If no error has occurred, the `callback` should be run - * without arguments or with an explicit `null` argument. The array index is not - * passed to the iteratee. Invoked with (item, callback). If you need the index, - * use `eachOf`. + * @param {AsyncFunction} iteratee - An async function to apply to + * each item in `coll`. Invoked with (item, callback). + * The array index is not passed to the iteratee. + * If you need the index, use `eachOf`. * @param {Function} [callback] - A callback which is called when all * `iteratee` functions have finished, or an error occurs. Invoked with (err). * @example diff --git a/lib/index.js b/lib/index.js index 0bb7efe19..39657d50b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,11 +1,45 @@ +/** + * An "async function" in the context of Async is an asynchronous function with + * a variable number of parameters, with the final parameter being a callback. + * (`function (arg1, arg2, ..., callback) {}`) + * The final callback is of the form `callback(err, results...)`, which must be + * called once the function is completed. The callback can be called with a + * Error as its first argument to signal that an error occurred. + * Otherwise, it can be called with `null` as the first argument, and any + * additional `result` arguments that may apply to signal successful completion. + * The callback must be called exactly once, ideally on a later tick of the + * JavaScript event loop. + * + * This type of function is also referred to as a "Node-style async function". + * + * Wherever we accept a Node-style async function, we also directly accept an + * ES2017 `async` function. + * In this case, the `async` function will not be passed a callback, and any + * thrown error will be used as the `err` argument of a theoretical callback, + * and the return value will be used as the `result` value. + * + * Note that we can only detect native `async function`s in this case. + * Your environment must have `async`/`await` support for this to work. + * (e.g. Node > v7.6, or a recent version of a modern browser). + * If you are using `async` functions through a transpiler (e.g. Babel), you + * must still wrap the function with [asyncify]{@link module:Utils.asyncify}, + * because the `async function` will be compiled to an ordinary function that + * returns a promise. + * + * @typedef {Function} AsyncFunction + * @static + */ + /** * Async is a utility module which provides straight-forward, powerful functions * for working with asynchronous JavaScript. Although originally designed for * use with [Node.js](http://nodejs.org) and installable via * `npm install --save async`, it can also be used directly in the browser. * @module async + * @see AsyncFunction */ + /** * A collection of `async` functions for manipulating collections, such as * arrays and objects. @@ -17,10 +51,11 @@ * @module ControlFlow */ - /** - * A collection of `async` utility functions. - * @module Utils - */ +/** + * A collection of `async` utility functions. + * @module Utils + */ + import applyEach from './applyEach'; import applyEachSeries from './applyEachSeries'; import apply from './apply'; From faf395c546747c2066e673405e0aec94362811a0 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Fri, 31 Mar 2017 14:00:58 -0700 Subject: [PATCH 13/16] clarify AsyncFunction docs --- lib/asyncify.js | 5 +++-- lib/index.js | 13 ++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/asyncify.js b/lib/asyncify.js index 61794fb05..1f0955e5f 100644 --- a/lib/asyncify.js +++ b/lib/asyncify.js @@ -12,7 +12,7 @@ import initialParams from './internal/initialParams'; * resolved/rejected state will be used to call the callback, rather than simply * the synchronous return value. * - * This also means you can asyncify ES2016 `async` functions. + * This also means you can asyncify ES2017 `async` functions. * * @name asyncify * @static @@ -48,7 +48,8 @@ import initialParams from './internal/initialParams'; * } * ], callback); * - * // es2017 example + * // 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) diff --git a/lib/index.js b/lib/index.js index 39657d50b..0fbeefca8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -13,12 +13,15 @@ * This type of function is also referred to as a "Node-style async function". * * Wherever we accept a Node-style async function, we also directly accept an - * ES2017 `async` function. - * In this case, the `async` function will not be passed a callback, and any - * thrown error will be used as the `err` argument of a theoretical callback, - * and the return value will be used as the `result` value. + * [ES2017 `async` function]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function}. + * In this case, the `async` function will not be passed a final callback + * argument, and any thrown error will be used as the `err` argument of the + * implicit callback, and the return value will be used as the `result` value. + * (i.e. a `rejected` of the returned Promise becomes the `err` callback + * argument, and a `resolved` value becomes the `result`.) * - * Note that we can only detect native `async function`s in this case. + * Note, due to JavaScript limitations, we can only detect native `async` + * functions and not transpilied implementations. * Your environment must have `async`/`await` support for this to work. * (e.g. Node > v7.6, or a recent version of a modern browser). * If you are using `async` functions through a transpiler (e.g. Babel), you From 94a8b2d18bf10cca67cf4c308253f642bbb2b160 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Sat, 1 Apr 2017 15:05:52 -0700 Subject: [PATCH 14/16] use the AsyncFunction type in all docs :sweat: --- lib/applyEach.js | 2 +- lib/applyEachSeries.js | 2 +- lib/asyncify.js | 8 ++++---- lib/auto.js | 8 ++++---- lib/autoInject.js | 2 +- lib/cargo.js | 5 ++--- lib/compose.js | 2 +- lib/concat.js | 6 ++---- lib/concatSeries.js | 5 ++--- lib/constant.js | 2 +- lib/detect.js | 6 +++--- lib/detectLimit.js | 6 +++--- lib/detectSeries.js | 6 +++--- lib/dir.js | 13 +++++++------ lib/doDuring.js | 7 +++---- lib/doUntil.js | 16 ++++++++-------- lib/doWhilst.js | 7 +++---- lib/during.js | 7 +++---- lib/eachLimit.js | 11 +++++------ lib/eachOf.js | 10 ++++------ lib/eachOfLimit.js | 8 +++----- lib/eachOfSeries.js | 8 +++----- lib/eachSeries.js | 11 +++++------ lib/ensureAsync.js | 7 ++++--- lib/every.js | 8 ++++---- lib/everyLimit.js | 8 ++++---- lib/everySeries.js | 8 ++++---- lib/forever.js | 8 ++++---- lib/groupBy.js | 8 ++++---- lib/groupByLimit.js | 8 ++++---- lib/groupBySeries.js | 8 ++++---- lib/index.js | 12 ++++++++---- lib/log.js | 4 ++-- lib/map.js | 8 ++++---- lib/mapLimit.js | 8 ++++---- lib/mapSeries.js | 8 ++++---- lib/mapValues.js | 8 ++++---- lib/mapValuesLimit.js | 8 ++++---- lib/mapValuesSeries.js | 8 ++++---- lib/memoize.js | 6 +++--- lib/parallel.js | 9 +++++---- lib/parallelLimit.js | 7 +++---- lib/priorityQueue.js | 9 ++++----- lib/queue.js | 8 +++----- lib/race.js | 5 ++--- lib/reduce.js | 12 ++++++------ lib/reduceRight.js | 12 ++++++------ lib/reflect.js | 8 ++++---- lib/reflectAll.js | 7 ++++--- lib/reject.js | 7 ++++--- lib/rejectLimit.js | 7 ++++--- lib/rejectSeries.js | 7 ++++--- lib/retry.js | 12 +++++------- lib/retryable.js | 14 +++++++++----- lib/seq.js | 2 +- lib/series.js | 6 +++--- lib/someLimit.js | 8 ++++---- lib/someSeries.js | 8 ++++---- lib/sortBy.js | 9 +++++---- lib/timeout.js | 9 ++++----- lib/times.js | 4 ++-- lib/timesLimit.js | 4 ++-- lib/timesSeries.js | 4 ++-- lib/transform.js | 7 ++----- lib/unmemoize.js | 4 ++-- lib/whilst.js | 5 ++--- 66 files changed, 237 insertions(+), 248 deletions(-) diff --git a/lib/applyEach.js b/lib/applyEach.js index d1dfc7889..93d8af3cd 100644 --- a/lib/applyEach.js +++ b/lib/applyEach.js @@ -13,7 +13,7 @@ import map from './map'; * @memberOf module:ControlFlow * @method * @category Control Flow - * @param {Array|Iterable|Object} fns - A collection of asynchronous functions + * @param {Array|Iterable|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. diff --git a/lib/applyEachSeries.js b/lib/applyEachSeries.js index 5a7c257bf..392b1396e 100644 --- a/lib/applyEachSeries.js +++ b/lib/applyEachSeries.js @@ -10,7 +10,7 @@ import mapSeries from './mapSeries'; * @method * @see [async.applyEach]{@link module:ControlFlow.applyEach} * @category Control Flow - * @param {Array|Iterable|Object} fns - A collection of asynchronous functions to all + * @param {Array|Iterable|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. diff --git a/lib/asyncify.js b/lib/asyncify.js index 1f0955e5f..96eea543b 100644 --- a/lib/asyncify.js +++ b/lib/asyncify.js @@ -20,10 +20,10 @@ import initialParams from './internal/initialParams'; * @method * @alias wrapSync * @category Util - * @param {Function} func - The synchronous function to convert to an - * asynchronous function. - * @returns {Function} An asynchronous wrapper of the `func`. To be invoked with - * (callback). + * @param {Function} func - The synchronous funuction, 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 diff --git a/lib/auto.js b/lib/auto.js index 4d7bf32d3..ba0fdc337 100644 --- a/lib/auto.js +++ b/lib/auto.js @@ -11,17 +11,17 @@ import onlyOnce from './internal/onlyOnce'; import wrapAsync from './internal/wrapAsync'; /** - * Determines the best order for running the functions in `tasks`, based on + * 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 functions pass an error to their callback, the `auto` sequence + * 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. * - * Functions also receive an object containing the results of functions which + * {@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. * @@ -31,7 +31,7 @@ import wrapAsync from './internal/wrapAsync'; * @method * @category Control Flow * @param {Object} tasks - An object. Each of its properties is either a - * function or an array of requirements, with the function itself the last item + * 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: diff --git a/lib/autoInject.js b/lib/autoInject.js index 3ba87fcc5..bc8ddd6ef 100644 --- a/lib/autoInject.js +++ b/lib/autoInject.js @@ -40,7 +40,7 @@ function parseParams(func) { * @method * @see [async.auto]{@link module:ControlFlow.auto} * @category Control Flow - * @param {Object} tasks - An object, each of whose properties is a function of + * @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. diff --git a/lib/cargo.js b/lib/cargo.js index d86cb67b6..5060223fa 100644 --- a/lib/cargo.js +++ b/lib/cargo.js @@ -48,9 +48,8 @@ import queue from './internal/queue'; * @method * @see [async.queue]{@link module:ControlFlow.queue} * @category Control Flow - * @param {Function} worker - An asynchronous function for processing an array - * of queued tasks, which must call its `callback(err)` argument when finished, - * with an optional `err` argument. Invoked with `(tasks, callback)`. + * @param {AsyncFunction} worker - An asynchronous function for processing an array + * of queued tasks. Invoked with `(tasks, callback)`. * @param {number} [payload=Infinity] - An optional `integer` for determining * how many tasks should be processed per round; if omitted, the default is * unlimited. diff --git a/lib/compose.js b/lib/compose.js index 5c64593f6..96632c05b 100644 --- a/lib/compose.js +++ b/lib/compose.js @@ -14,7 +14,7 @@ import rest from './internal/rest'; * @memberOf module:ControlFlow * @method * @category Control Flow - * @param {...Function} functions - the asynchronous functions to compose + * @param {...AsyncFunction} functions - the asynchronous functions to compose * @returns {Function} an asynchronous function that is the composed * asynchronous `functions` * @example diff --git a/lib/concat.js b/lib/concat.js index ba4ea1314..7edf75937 100644 --- a/lib/concat.js +++ b/lib/concat.js @@ -14,10 +14,8 @@ import doParallel from './internal/doParallel'; * @method * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A function to apply to each item in `coll`. - * The iteratee is passed a `callback(err, results)` which must be called once - * it has completed with an error (which can be `null`) and an array of results. - * Invoked with (item, callback). + * @param {AsyncFunction} iteratee - A function to apply to each item in `coll`, + * which should use an array as its result. Invoked with (item, callback). * @param {Function} [callback(err)] - A callback which is called after all the * `iteratee` functions have finished, or an error occurs. Results is an array * containing the concatenated results of the `iteratee` function. Invoked with diff --git a/lib/concatSeries.js b/lib/concatSeries.js index 77b439bc8..f5b3fab9c 100644 --- a/lib/concatSeries.js +++ b/lib/concatSeries.js @@ -11,9 +11,8 @@ import doSeries from './internal/doSeries'; * @see [async.concat]{@link module:Collections.concat} * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A function to apply to each item in `coll`. - * The iteratee is passed a `callback(err, results)` which must be called once - * it has completed with an error (which can be `null`) and an array of results. + * @param {AsyncFunction} iteratee - A function to apply to each item in `coll`. + * The iteratee should complete with an array an array of results. * Invoked with (item, callback). * @param {Function} [callback(err)] - A callback which is called after all the * `iteratee` functions have finished, or an error occurs. Results is an array diff --git a/lib/constant.js b/lib/constant.js index c9c0c1e72..ae6ffd099 100644 --- a/lib/constant.js +++ b/lib/constant.js @@ -13,7 +13,7 @@ import initialParams from './internal/initialParams'; * @category Util * @param {...*} arguments... - Any number of arguments to automatically invoke * callback with. - * @returns {Function} Returns a function that when invoked, automatically + * @returns {AsyncFunction} Returns a function that when invoked, automatically * invokes the callback with the previous given arguments. * @example * diff --git a/lib/detect.js b/lib/detect.js index 5f5a7ef9f..654a370fa 100644 --- a/lib/detect.js +++ b/lib/detect.js @@ -21,9 +21,9 @@ import findGetResult from './internal/findGetResult'; * @alias find * @category Collections * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A truth test to apply to each item in `coll`. - * The iteratee is passed a `callback(err, truthValue)` which must be called - * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`. + * The iteratee must complete with a boolean value as its result. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called as soon as any * iteratee returns `true`, or after all the `iteratee` functions have finished. * Result will be the first item in the array that passes the truth test diff --git a/lib/detectLimit.js b/lib/detectLimit.js index d0f2a92af..bba10c839 100644 --- a/lib/detectLimit.js +++ b/lib/detectLimit.js @@ -17,9 +17,9 @@ import findGetResult from './internal/findGetResult'; * @category Collections * @param {Array|Iterable|Object} coll - A collection to iterate over. * @param {number} limit - The maximum number of async operations at a time. - * @param {Function} iteratee - A truth test to apply to each item in `coll`. - * The iteratee is passed a `callback(err, truthValue)` which must be called - * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`. + * The iteratee must complete with a boolean value as its result. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called as soon as any * iteratee returns `true`, or after all the `iteratee` functions have finished. * Result will be the first item in the array that passes the truth test diff --git a/lib/detectSeries.js b/lib/detectSeries.js index 51413df39..684bdaaab 100644 --- a/lib/detectSeries.js +++ b/lib/detectSeries.js @@ -12,9 +12,9 @@ import doLimit from './internal/doLimit'; * @alias findSeries * @category Collections * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A truth test to apply to each item in `coll`. - * The iteratee is passed a `callback(err, truthValue)` which must be called - * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`. + * The iteratee must complete with a boolean value as its result. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called as soon as any * iteratee returns `true`, or after all the `iteratee` functions have finished. * Result will be the first item in the array that passes the truth test diff --git a/lib/dir.js b/lib/dir.js index 912b558ae..8a5b5e3ee 100644 --- a/lib/dir.js +++ b/lib/dir.js @@ -1,10 +1,11 @@ import consoleFunc from './internal/consoleFunc'; /** - * Logs the result of an `async` function to the `console` using `console.dir` - * to display the properties of the resulting object. Only works in Node.js or - * in browsers that support `console.dir` and `console.error` (such as FF and - * Chrome). If multiple arguments are returned from the async function, + * Logs the result of an [`async` function]{@link AsyncFunction} to the + * `console` using `console.dir` to display the properties of the resulting object. + * Only works in Node.js or in browsers that support `console.dir` and + * `console.error` (such as FF and Chrome). + * If multiple arguments are returned from the async function, * `console.dir` is called on each argument in order. * * @name dir @@ -12,8 +13,8 @@ import consoleFunc from './internal/consoleFunc'; * @memberOf module:Utils * @method * @category Util - * @param {Function} function - The function you want to eventually apply all - * arguments to. + * @param {AsyncFunction} function - The function you want to eventually apply + * all arguments to. * @param {...*} arguments... - Any number of arguments to apply to the function. * @example * diff --git a/lib/doDuring.js b/lib/doDuring.js index e79574b55..7b52c22c2 100644 --- a/lib/doDuring.js +++ b/lib/doDuring.js @@ -14,10 +14,9 @@ import wrapAsync from './internal/wrapAsync'; * @method * @see [async.during]{@link module:ControlFlow.during} * @category Control Flow - * @param {Function} fn - A function which is called each time `test` passes. - * The function is passed a `callback(err)`, which must be called once it has - * completed with an optional `err` argument. Invoked with (callback). - * @param {Function} test - asynchronous truth test to perform before each + * @param {AsyncFunction} fn - An async function which is called each time + * `test` passes. Invoked with (callback). + * @param {AsyncFunction} test - asynchronous truth test to perform before each * execution of `fn`. Invoked with (...args, callback), where `...args` are the * non-error args from the previous callback of `fn`. * @param {Function} [callback] - A callback which is called after the test diff --git a/lib/doUntil.js b/lib/doUntil.js index 0c5bb7194..06d3af77f 100644 --- a/lib/doUntil.js +++ b/lib/doUntil.js @@ -10,18 +10,18 @@ import doWhilst from './doWhilst'; * @method * @see [async.doWhilst]{@link module:ControlFlow.doWhilst} * @category Control Flow - * @param {Function} fn - A function which is called each time `test` fails. - * The function is passed a `callback(err)`, which must be called once it has - * completed with an optional `err` argument. Invoked with (callback). + * @param {AsyncFunction} iteratee - An async function which is called each time + * `test` fails. Invoked with (callback). * @param {Function} test - synchronous truth test to perform after each - * execution of `fn`. Invoked with the non-error callback results of `fn`. + * execution of `iteratee`. Invoked with any non-error callback results of + * `iteratee`. * @param {Function} [callback] - A callback which is called after the test - * function has passed and repeated execution of `fn` has stopped. `callback` - * will be passed an error and any arguments passed to the final `fn`'s + * function has passed and repeated execution of `iteratee` has stopped. `callback` + * will be passed an error and any arguments passed to the final `iteratee`'s * callback. Invoked with (err, [results]); */ -export default function doUntil(fn, test, callback) { - doWhilst(fn, function() { +export default function doUntil(iteratee, test, callback) { + doWhilst(iteratee, function() { return !test.apply(this, arguments); }, callback); } diff --git a/lib/doWhilst.js b/lib/doWhilst.js index 39f460008..4407305f6 100644 --- a/lib/doWhilst.js +++ b/lib/doWhilst.js @@ -16,11 +16,10 @@ import wrapAsync from './internal/wrapAsync'; * @method * @see [async.whilst]{@link module:ControlFlow.whilst} * @category Control Flow - * @param {Function} iteratee - A function which is called each time `test` - * passes. The function is passed a `callback(err)`, which must be called once - * it has completed with an optional `err` argument. Invoked with (callback). + * @param {AsyncFunction} iteratee - A function which is called each time `test` + * passes. Invoked with (callback). * @param {Function} test - synchronous truth test to perform after each - * execution of `iteratee`. Invoked with the non-error callback results of + * execution of `iteratee`. Invoked with any non-error callback results of * `iteratee`. * @param {Function} [callback] - A callback which is called after the test * function has failed and repeated execution of `iteratee` has stopped. diff --git a/lib/during.js b/lib/during.js index dd9ba997a..2f91dd5a8 100644 --- a/lib/during.js +++ b/lib/during.js @@ -14,11 +14,10 @@ import wrapAsync from './internal/wrapAsync'; * @method * @see [async.whilst]{@link module:ControlFlow.whilst} * @category Control Flow - * @param {Function} test - asynchronous truth test to perform before each + * @param {AsyncFunction} test - asynchronous truth test to perform before each * execution of `fn`. Invoked with (callback). - * @param {Function} fn - A function which is called each time `test` passes. - * The function is passed a `callback(err)`, which must be called once it has - * completed with an optional `err` argument. Invoked with (callback). + * @param {AsyncFunction} fn - An async function which is called each time + * `test` passes. Invoked with (callback). * @param {Function} [callback] - A callback which is called after the test * function has failed and repeated execution of `fn` has stopped. `callback` * will be passed an error, if one occured, otherwise `null`. diff --git a/lib/eachLimit.js b/lib/eachLimit.js index c3bc595d0..462e329ee 100644 --- a/lib/eachLimit.js +++ b/lib/eachLimit.js @@ -14,12 +14,11 @@ import wrapAsync from './internal/wrapAsync'; * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. * @param {number} limit - The maximum number of async operations at a time. - * @param {Function} iteratee - A function to apply to each item in `coll`. The - * iteratee is passed a `callback(err)` which must be called once it has - * completed. If no error has occurred, the `callback` should be run without - * arguments or with an explicit `null` argument. The array index is not passed - * to the iteratee. Invoked with (item, callback). If you need the index, use - * `eachOfLimit`. + * @param {AsyncFunction} iteratee - An async function to apply to each item in + * `coll`. + * The array index is not passed to the iteratee. + * If you need the index, use `eachOfLimit`. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called when all * `iteratee` functions have finished, or an error occurs. Invoked with (err). */ diff --git a/lib/eachOf.js b/lib/eachOf.js index 1cfa171dc..9ab3eb6d5 100644 --- a/lib/eachOf.js +++ b/lib/eachOf.js @@ -46,12 +46,10 @@ var eachOfGeneric = doLimit(eachOfLimit, Infinity); * @category Collection * @see [async.each]{@link module:Collections.each} * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} 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. The iteratee is passed a `callback(err)` which must be called once it - * has completed. If no error has occurred, the callback should be run without - * arguments or with an explicit `null` argument. Invoked with - * (item, key, callback). + * @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). * @example diff --git a/lib/eachOfLimit.js b/lib/eachOfLimit.js index 17dffc11b..1de28efc6 100644 --- a/lib/eachOfLimit.js +++ b/lib/eachOfLimit.js @@ -14,12 +14,10 @@ import wrapAsync from './internal/wrapAsync'; * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. * @param {number} limit - The maximum number of async operations at a time. - * @param {Function} iteratee - A function to apply to each + * @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. The iteratee is passed a `callback(err)` which must be called once it - * has completed. If no error has occurred, the callback should be run without - * arguments or with an explicit `null` argument. Invoked with - * (item, key, callback). + * 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). */ diff --git a/lib/eachOfSeries.js b/lib/eachOfSeries.js index 21e816d37..fc1a09724 100644 --- a/lib/eachOfSeries.js +++ b/lib/eachOfSeries.js @@ -12,11 +12,9 @@ import doLimit from './internal/doLimit'; * @alias forEachOfSeries * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} 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. The iteratee is - * passed a `callback(err)` which must be called once it has completed. If no - * error has occurred, the callback should be run without arguments or with an - * explicit `null` argument. Invoked with (item, key, callback). + * @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). */ diff --git a/lib/eachSeries.js b/lib/eachSeries.js index 0839d8ca1..9f8d7defd 100644 --- a/lib/eachSeries.js +++ b/lib/eachSeries.js @@ -12,12 +12,11 @@ import doLimit from './internal/doLimit'; * @alias forEachSeries * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A function to apply to each - * item in `coll`. The iteratee is passed a `callback(err)` which must be called - * once it has completed. If no error has occurred, the `callback` should be run - * without arguments or with an explicit `null` argument. The array index is - * not passed to the iteratee. Invoked with (item, callback). If you need the - * index, use `eachOfSeries`. + * @param {AsyncFunction} iteratee - An async function to apply to each + * item in `coll`. + * The array index is not passed to the iteratee. + * If you need the index, use `eachOfSeries`. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called when all * `iteratee` functions have finished, or an error occurs. Invoked with (err). */ diff --git a/lib/ensureAsync.js b/lib/ensureAsync.js index 8582bbabd..a579efca6 100644 --- a/lib/ensureAsync.js +++ b/lib/ensureAsync.js @@ -8,16 +8,17 @@ import { isAsync } from './internal/wrapAsync'; * no extra deferral is added. This is useful for preventing stack overflows * (`RangeError: Maximum call stack size exceeded`) and generally keeping * [Zalgo](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony) - * contained. + * contained. ES2017 `async` functions are returned as-is -- they are immune + * to Zalgo's corrupting influences, as they always resolve on a later tick. * * @name ensureAsync * @static * @memberOf module:Utils * @method * @category Util - * @param {Function} fn - an async function, one that expects a node-style + * @param {AsyncFunction} fn - an async function, one that expects a node-style * callback as its last argument. - * @returns {Function} Returns a wrapped function with the exact same call + * @returns {AsyncFunction} Returns a wrapped function with the exact same call * signature as the function passed in. * @example * diff --git a/lib/every.js b/lib/every.js index b34ad1f93..eb1074807 100644 --- a/lib/every.js +++ b/lib/every.js @@ -13,10 +13,10 @@ import notId from './internal/notId'; * @alias all * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A truth test to apply to each item in the - * collection in parallel. The iteratee is passed a `callback(err, truthValue)` - * which must be called with a boolean argument once it has completed. Invoked - * with (item, callback). + * @param {AsyncFunction} iteratee - An async truth test to apply to each item + * in the collection in parallel. + * The iteratee must complete with a boolean result value. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Result will be either `true` or `false` * depending on the values of the async tests. Invoked with (err, result). diff --git a/lib/everyLimit.js b/lib/everyLimit.js index 4cc709147..89a69fc01 100644 --- a/lib/everyLimit.js +++ b/lib/everyLimit.js @@ -14,10 +14,10 @@ import notId from './internal/notId'; * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. * @param {number} limit - The maximum number of async operations at a time. - * @param {Function} iteratee - A truth test to apply to each item in the - * collection in parallel. The iteratee is passed a `callback(err, truthValue)` - * which must be called with a boolean argument once it has completed. Invoked - * with (item, callback). + * @param {AsyncFunction} iteratee - An async truth test to apply to each item + * in the collection in parallel. + * The iteratee must complete with a boolean result value. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Result will be either `true` or `false` * depending on the values of the async tests. Invoked with (err, result). diff --git a/lib/everySeries.js b/lib/everySeries.js index 743474805..b67368c95 100644 --- a/lib/everySeries.js +++ b/lib/everySeries.js @@ -12,10 +12,10 @@ import doLimit from './internal/doLimit'; * @alias allSeries * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A truth test to apply to each item in the - * collection in parallel. The iteratee is passed a `callback(err, truthValue)` - * which must be called with a boolean argument once it has completed. Invoked - * with (item, callback). + * @param {AsyncFunction} iteratee - An async truth test to apply to each item + * in the collection in series. + * The iteratee must complete with a boolean result value. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Result will be either `true` or `false` * depending on the values of the async tests. Invoked with (err, result). diff --git a/lib/forever.js b/lib/forever.js index 66bc47276..f7be0b257 100644 --- a/lib/forever.js +++ b/lib/forever.js @@ -8,16 +8,16 @@ import wrapAsync from './internal/wrapAsync'; * Calls the asynchronous function `fn` with a callback parameter that allows it * to call itself again, in series, indefinitely. - * If an error is passed to the - * callback then `errback` is called with the error, and execution stops, - * otherwise it will never be called. + * If an error is passed to the callback then `errback` is called with the + * error, and execution stops, otherwise it will never be called. * * @name forever * @static * @memberOf module:ControlFlow * @method * @category Control Flow - * @param {Function} fn - a function to call repeatedly. Invoked with (next). + * @param {AsyncFunction} fn - an async function to call repeatedly. + * Invoked with (next). * @param {Function} [errback] - when `fn` passes an error to it's callback, * this function will be called, and execution stops. Invoked with (err). * @example diff --git a/lib/groupBy.js b/lib/groupBy.js index 31e2c0b75..a2f923ff5 100644 --- a/lib/groupBy.js +++ b/lib/groupBy.js @@ -18,10 +18,10 @@ import groupByLimit from './groupByLimit'; * @method * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A function to apply to each item in `coll`. - * The iteratee is passed a `callback(err, key)` which must be called once it - * has completed with an error (which can be `null`) and the `key` to group the - * value under. Invoked with (value, callback). + * @param {AsyncFunction} iteratee - An async function to apply to each item in + * `coll`. + * The iteratee should complete with a `key` to group the value under. + * Invoked with (value, callback). * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. Result is an `Object` whoses * properties are arrays of values which returned the corresponding key. diff --git a/lib/groupByLimit.js b/lib/groupByLimit.js index c14835191..b0a5ef339 100644 --- a/lib/groupByLimit.js +++ b/lib/groupByLimit.js @@ -12,10 +12,10 @@ import wrapAsync from './internal/wrapAsync'; * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. * @param {number} limit - The maximum number of async operations at a time. - * @param {Function} iteratee - A function to apply to each item in `coll`. - * The iteratee is passed a `callback(err, key)` which must be called once it - * has completed with an error (which can be `null`) and the `key` to group the - * value under. Invoked with (value, callback). + * @param {AsyncFunction} iteratee - An async function to apply to each item in + * `coll`. + * The iteratee should complete with a `key` to group the value under. + * Invoked with (value, callback). * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. Result is an `Object` whoses * properties are arrays of values which returned the corresponding key. diff --git a/lib/groupBySeries.js b/lib/groupBySeries.js index 5df9a3cbe..91935c378 100644 --- a/lib/groupBySeries.js +++ b/lib/groupBySeries.js @@ -12,10 +12,10 @@ import groupByLimit from './groupByLimit'; * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. * @param {number} limit - The maximum number of async operations at a time. - * @param {Function} iteratee - A function to apply to each item in `coll`. - * The iteratee is passed a `callback(err, key)` which must be called once it - * has completed with an error (which can be `null`) and the `key` to group the - * value under. Invoked with (value, callback). + * @param {AsyncFunction} iteratee - An async function to apply to each item in + * `coll`. + * The iteratee should complete with a `key` to group the value under. + * Invoked with (value, callback). * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. Result is an `Object` whoses * properties are arrays of values which returned the corresponding key. diff --git a/lib/index.js b/lib/index.js index 0fbeefca8..f1e8e988c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,14 +3,18 @@ * a variable number of parameters, with the final parameter being a callback. * (`function (arg1, arg2, ..., callback) {}`) * The final callback is of the form `callback(err, results...)`, which must be - * called once the function is completed. The callback can be called with a + * called once the function is completed. The callback should be called with a * Error as its first argument to signal that an error occurred. - * Otherwise, it can be called with `null` as the first argument, and any - * additional `result` arguments that may apply to signal successful completion. + * Otherwise, if no error occurred, it should be called with `null` as the first + * argument, and any additional `result` arguments that may apply, to signal + * successful completion. * The callback must be called exactly once, ideally on a later tick of the * JavaScript event loop. * - * This type of function is also referred to as a "Node-style async function". + * This type of function is also referred to as a "Node-style async function", + * or a "continuation passing-style function" (CPS). Most of the methods of this + * library are themselves CPS/Node-style async functions, or functions that + * return CPS/Node-style async functions. * * Wherever we accept a Node-style async function, we also directly accept an * [ES2017 `async` function]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function}. diff --git a/lib/log.js b/lib/log.js index 848661da5..c39e03352 100644 --- a/lib/log.js +++ b/lib/log.js @@ -11,8 +11,8 @@ import consoleFunc from './internal/consoleFunc'; * @memberOf module:Utils * @method * @category Util - * @param {Function} function - The function you want to eventually apply all - * arguments to. + * @param {AsyncFunction} function - The function you want to eventually apply + * all arguments to. * @param {...*} arguments... - Any number of arguments to apply to the function. * @example * diff --git a/lib/map.js b/lib/map.js index 76d60582b..100ab7a1a 100644 --- a/lib/map.js +++ b/lib/map.js @@ -24,10 +24,10 @@ import map from './internal/map'; * @method * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A function to apply to each item in `coll`. - * The iteratee is passed a `callback(err, transformed)` which must be called - * once it has completed with an error (which can be `null`) and a - * transformed item. Invoked with (item, callback). + * @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). diff --git a/lib/mapLimit.js b/lib/mapLimit.js index a54922d3b..0a2fc02c4 100644 --- a/lib/mapLimit.js +++ b/lib/mapLimit.js @@ -12,10 +12,10 @@ import map from './internal/map'; * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. * @param {number} limit - The maximum number of async operations at a time. - * @param {Function} iteratee - A function to apply to each item in `coll`. - * The iteratee is passed a `callback(err, transformed)` which must be called - * once it has completed with an error (which can be `null`) and a transformed - * item. Invoked with (item, callback). + * @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). diff --git a/lib/mapSeries.js b/lib/mapSeries.js index e1f3d0d5d..596c75ec7 100644 --- a/lib/mapSeries.js +++ b/lib/mapSeries.js @@ -11,10 +11,10 @@ import doLimit from './internal/doLimit'; * @see [async.map]{@link module:Collections.map} * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A function to apply to each item in `coll`. - * The iteratee is passed a `callback(err, transformed)` which must be called - * once it has completed with an error (which can be `null`) and a - * transformed item. Invoked with (item, callback). + * @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). diff --git a/lib/mapValues.js b/lib/mapValues.js index afc43f482..d6a4ad23f 100644 --- a/lib/mapValues.js +++ b/lib/mapValues.js @@ -21,10 +21,10 @@ import doLimit from './internal/doLimit'; * @method * @category Collection * @param {Object} obj - A collection to iterate over. - * @param {Function} iteratee - A function to apply to each value and key in - * `coll`. The iteratee is passed a `callback(err, transformed)` which must be - * called once it has completed with an error (which can be `null`) and a - * transformed value. Invoked with (value, key, callback). + * @param {AsyncFunction} iteratee - A function to apply to each value and key + * in `coll`. + * The iteratee should complete with the transformed value as its result. + * Invoked with (value, key, callback). * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. `result` is a new object consisting * of each key from `obj`, with each transformed value on the right-hand side. diff --git a/lib/mapValuesLimit.js b/lib/mapValuesLimit.js index 1d5a47a85..c6e020d03 100644 --- a/lib/mapValuesLimit.js +++ b/lib/mapValuesLimit.js @@ -16,10 +16,10 @@ import wrapAsync from './internal/wrapAsync'; * @category Collection * @param {Object} obj - A collection to iterate over. * @param {number} limit - The maximum number of async operations at a time. - * @param {Function} iteratee - A function to apply to each value in `obj`. - * The iteratee is passed a `callback(err, transformed)` which must be called - * once it has completed with an error (which can be `null`) and a - * transformed value. Invoked with (value, key, callback). + * @param {AsyncFunction} iteratee - A function to apply to each value and key + * in `coll`. + * The iteratee should complete with the transformed value as its result. + * Invoked with (value, key, callback). * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. `result` is a new object consisting * of each key from `obj`, with each transformed value on the right-hand side. diff --git a/lib/mapValuesSeries.js b/lib/mapValuesSeries.js index c1530c239..ab0b8f845 100644 --- a/lib/mapValuesSeries.js +++ b/lib/mapValuesSeries.js @@ -11,10 +11,10 @@ import doLimit from './internal/doLimit'; * @see [async.mapValues]{@link module:Collections.mapValues} * @category Collection * @param {Object} obj - A collection to iterate over. - * @param {Function} iteratee - A function to apply to each value in `obj`. - * The iteratee is passed a `callback(err, transformed)` which must be called - * once it has completed with an error (which can be `null`) and a - * transformed value. Invoked with (value, key, callback). + * @param {AsyncFunction} iteratee - A function to apply to each value and key + * in `coll`. + * The iteratee should complete with the transformed value as its result. + * Invoked with (value, key, callback). * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. `result` is a new object consisting * of each key from `obj`, with each transformed value on the right-hand side. diff --git a/lib/memoize.js b/lib/memoize.js index d752d999a..de71c33e7 100644 --- a/lib/memoize.js +++ b/lib/memoize.js @@ -10,7 +10,7 @@ function has(obj, key) { } /** - * Caches the results of an `async` function. When creating a hash to store + * Caches the results of an async function. When creating a hash to store * function results against, the callback is omitted from the hash and an * optional hash function can be used. * @@ -28,11 +28,11 @@ function has(obj, key) { * @memberOf module:Utils * @method * @category Util - * @param {Function} fn - The function to proxy and cache results from. + * @param {AsyncFunction} fn - The async function to proxy and cache results from. * @param {Function} hasher - An optional function for generating a custom hash * for storing results. It has all the arguments applied to it apart from the * callback, and must be synchronous. - * @returns {Function} a memoized version of `fn` + * @returns {AsyncFunction} a memoized version of `fn` * @example * * var slow_fn = function(name, callback) { diff --git a/lib/parallel.js b/lib/parallel.js index 19d555884..8b6c2ce89 100644 --- a/lib/parallel.js +++ b/lib/parallel.js @@ -13,6 +13,7 @@ import parallel from './internal/parallel'; * any I/O, they will actually be executed in series. Any synchronous setup * sections for each task will happen one after the other. JavaScript remains * single-threaded. + * * **Hint:** Use [`reflect`]{@link module:Utils.reflect} to continue the * execution of other tasks when a task fails. * @@ -26,14 +27,14 @@ import parallel from './internal/parallel'; * @memberOf module:ControlFlow * @method * @category Control Flow - * @param {Array|Iterable|Object} tasks - A collection containing functions to run. - * Each function is passed a `callback(err, result)` which it must call on - * completion with an error `err` (which can be `null`) and an optional `result` - * value. + * @param {Array|Iterable|Object} tasks - A collection of + * [async functions]{@link AsyncFunction} to run. + * Each async function can complete with any number of optional `result` values. * @param {Function} [callback] - An optional callback to run once all the * functions have completed successfully. This function gets a results array * (or object) containing all the result arguments passed to the task callbacks. * Invoked with (err, results). + * * @example * async.parallel([ * function(callback) { diff --git a/lib/parallelLimit.js b/lib/parallelLimit.js index 926fa853a..14e9e5230 100644 --- a/lib/parallelLimit.js +++ b/lib/parallelLimit.js @@ -11,10 +11,9 @@ import parallel from './internal/parallel'; * @method * @see [async.parallel]{@link module:ControlFlow.parallel} * @category Control Flow - * @param {Array|Collection} tasks - A collection containing functions to run. - * Each function is passed a `callback(err, result)` which it must call on - * completion with an error `err` (which can be `null`) and an optional `result` - * value. + * @param {Array|Iterable|Object} tasks - A collection of + * [async functions]{@link AsyncFunction} to run. + * Each async function can complete with any number of optional `result` values. * @param {number} limit - The maximum number of async operations at a time. * @param {Function} [callback] - An optional callback to run once all the * functions have completed successfully. This function gets a results array diff --git a/lib/priorityQueue.js b/lib/priorityQueue.js index 0af01577c..aff729379 100644 --- a/lib/priorityQueue.js +++ b/lib/priorityQueue.js @@ -15,11 +15,10 @@ import queue from './queue'; * @method * @see [async.queue]{@link module:ControlFlow.queue} * @category Control Flow - * @param {Function} worker - An asynchronous function for processing a queued - * task, which must call its `callback(err)` argument when finished, with an - * optional `error` as an argument. If you want to handle errors from an - * individual task, pass a callback to `q.push()`. Invoked with - * (task, callback). + * @param {AsyncFunction} worker - An async function for processing a queued task. + * If you want to handle errors from an individual task, pass a callback to + * `q.push()`. + * Invoked with (task, callback). * @param {number} concurrency - An `integer` for determining how many `worker` * functions should be run in parallel. If omitted, the concurrency defaults to * `1`. If the concurrency is `0`, an error is thrown. diff --git a/lib/queue.js b/lib/queue.js index 0f17cd996..906467c07 100644 --- a/lib/queue.js +++ b/lib/queue.js @@ -59,11 +59,9 @@ import wrapAsync from './internal/wrapAsync'; * @memberOf module:ControlFlow * @method * @category Control Flow - * @param {Function} worker - An asynchronous function for processing a queued - * task, which must call its `callback(err)` argument when finished, with an - * optional `error` as an argument. If you want to handle errors from an - * individual task, pass a callback to `q.push()`. Invoked with - * (task, callback). + * @param {AsyncFunction} worker - An async function for processing a queued task. + * If you want to handle errors from an individual task, pass a callback to + * `q.push()`. Invoked with (task, callback). * @param {number} [concurrency=1] - An `integer` for determining how many * `worker` functions should be run in parallel. If omitted, the concurrency * defaults to `1`. If the concurrency is `0`, an error is thrown. diff --git a/lib/race.js b/lib/race.js index ee81b37be..9b4d6c380 100644 --- a/lib/race.js +++ b/lib/race.js @@ -14,9 +14,8 @@ import wrapAsync from './internal/wrapAsync'; * @memberOf module:ControlFlow * @method * @category Control Flow - * @param {Array} tasks - An array containing functions to run. Each function - * is passed a `callback(err, result)` which it must call on completion with an - * error `err` (which can be `null`) and an optional `result` value. + * @param {Array} tasks - An array containing [async functions]{@link AsyncFunction} + * to run. Each function can complete with an optional `result` value. * @param {Function} callback - A callback to run once any of the functions have * completed. This function gets an error or result from the first function that * completed. Invoked with (err, result). diff --git a/lib/reduce.js b/lib/reduce.js index c5ac2921e..0d58c41e2 100644 --- a/lib/reduce.js +++ b/lib/reduce.js @@ -23,12 +23,12 @@ import wrapAsync from './internal/wrapAsync'; * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. * @param {*} memo - The initial state of the reduction. - * @param {Function} iteratee - A function applied to each item in the - * array to produce the next step in the reduction. The `iteratee` is passed a - * `callback(err, reduction)` which accepts an optional error as its first - * argument, and the state of the reduction as the second. If an error is - * passed to the callback, the reduction is stopped and the main `callback` is - * immediately called with the error. Invoked with (memo, item, callback). + * @param {AsyncFunction} iteratee - A function applied to each item in the + * array to produce the next step in the reduction. + * The `iteratee` should complete with the next state of the reduction. + * If the iteratee complete with an error, the reduction is stopped and the + * main `callback` is immediately called with the error. + * Invoked with (memo, item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Result is the reduced value. Invoked with * (err, result). diff --git a/lib/reduceRight.js b/lib/reduceRight.js index 844c94f93..ca5aa7fb0 100644 --- a/lib/reduceRight.js +++ b/lib/reduceRight.js @@ -14,12 +14,12 @@ var slice = Array.prototype.slice; * @category Collection * @param {Array} array - A collection to iterate over. * @param {*} memo - The initial state of the reduction. - * @param {Function} iteratee - A function applied to each item in the - * array to produce the next step in the reduction. The `iteratee` is passed a - * `callback(err, reduction)` which accepts an optional error as its first - * argument, and the state of the reduction as the second. If an error is - * passed to the callback, the reduction is stopped and the main `callback` is - * immediately called with the error. Invoked with (memo, item, callback). + * @param {AsyncFunction} iteratee - A function applied to each item in the + * array to produce the next step in the reduction. + * The `iteratee` should complete with the next state of the reduction. + * If the iteratee complete with an error, the reduction is stopped and the + * main `callback` is immediately called with the error. + * Invoked with (memo, item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Result is the reduced value. Invoked with * (err, result). diff --git a/lib/reflect.js b/lib/reflect.js index 0dbd397a6..9e0613b48 100644 --- a/lib/reflect.js +++ b/lib/reflect.js @@ -3,17 +3,17 @@ import rest from './internal/rest'; import wrapAsync from './internal/wrapAsync'; /** - * Wraps the function in another function that always returns data even when it - * errors. + * Wraps the async function in another function that always completes with a + * result object, even when it errors. * - * The object returned has either the property `error` or `value`. + * The result object has either the property `error` or `value`. * * @name reflect * @static * @memberOf module:Utils * @method * @category Util - * @param {Function} fn - The function you want to wrap + * @param {AsyncFunction} fn - The async function you want to wrap * @returns {Function} - A function that always passes null to it's callback as * the error. The second argument to the callback will be an `object` with * either an `error` or a `value` property. diff --git a/lib/reflectAll.js b/lib/reflectAll.js index dbeba8d81..0774b9432 100644 --- a/lib/reflectAll.js +++ b/lib/reflectAll.js @@ -4,7 +4,7 @@ import _arrayMap from 'lodash/_arrayMap'; import forOwn from 'lodash/_baseForOwn'; /** - * A helper function that wraps an array or an object of functions with reflect. + * A helper function that wraps an array or an object of functions with `reflect`. * * @name reflectAll * @static @@ -12,8 +12,9 @@ import forOwn from 'lodash/_baseForOwn'; * @method * @see [async.reflect]{@link module:Utils.reflect} * @category Util - * @param {Array} tasks - The array of functions to wrap in `async.reflect`. - * @returns {Array} Returns an array of functions, each function wrapped in + * @param {Array|Object|Iterable} tasks - The collection of + * [async functions]{@link AsyncFunction} to wrap in `async.reflect`. + * @returns {Array} Returns an array of async functions, each wrapped in * `async.reflect` * @example * diff --git a/lib/reject.js b/lib/reject.js index 0ddbaf0ac..95a627036 100644 --- a/lib/reject.js +++ b/lib/reject.js @@ -11,9 +11,10 @@ import doParallel from './internal/doParallel'; * @see [async.filter]{@link module:Collections.filter} * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A truth test to apply to each item in `coll`. - * The `iteratee` is passed a `callback(err, truthValue)`, which must be called - * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} iteratee - An async truth test to apply to each item in + * `coll`. + * The should complete with a boolean value as its `result`. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Invoked with (err, results). * @example diff --git a/lib/rejectLimit.js b/lib/rejectLimit.js index 977bc4c83..814781138 100644 --- a/lib/rejectLimit.js +++ b/lib/rejectLimit.js @@ -13,9 +13,10 @@ import doParallelLimit from './internal/doParallelLimit'; * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. * @param {number} limit - The maximum number of async operations at a time. - * @param {Function} iteratee - A truth test to apply to each item in `coll`. - * The `iteratee` is passed a `callback(err, truthValue)`, which must be called - * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} iteratee - An async truth test to apply to each item in + * `coll`. + * The should complete with a boolean value as its `result`. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Invoked with (err, results). */ diff --git a/lib/rejectSeries.js b/lib/rejectSeries.js index 0bb925e97..7eba2f736 100644 --- a/lib/rejectSeries.js +++ b/lib/rejectSeries.js @@ -11,9 +11,10 @@ import doLimit from './internal/doLimit'; * @see [async.reject]{@link module:Collections.reject} * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A truth test to apply to each item in `coll`. - * The `iteratee` is passed a `callback(err, truthValue)`, which must be called - * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} iteratee - An async truth test to apply to each item in + * `coll`. + * The should complete with a boolean value as its `result`. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Invoked with (err, results). */ diff --git a/lib/retry.js b/lib/retry.js index e2790160e..bd60644ce 100644 --- a/lib/retry.js +++ b/lib/retry.js @@ -13,6 +13,7 @@ import wrapAsync from './internal/wrapAsync'; * @memberOf module:ControlFlow * @method * @category Control Flow + * @see [async.retryable]{@link module:ControlFlow.retryable} * @param {Object|number} [opts = {times: 5, interval: 0}| 5] - Can be either an * object with `times` and `interval` or a number. * * `times` - The number of attempts to make before giving up. The default @@ -27,16 +28,13 @@ import wrapAsync from './internal/wrapAsync'; * Invoked with (err). * * If `opts` is a number, the number specifies the number of times to retry, * with the default interval of `0`. - * @param {Function} task - A function which receives two arguments: (1) a - * `callback(err, result)` which must be called when finished, passing `err` - * (which can be `null`) and the `result` of the function's execution, and (2) - * a `results` object, containing the results of the previously executed - * functions (if nested inside another control flow). Invoked with - * (callback, results). + * @param {AsyncFunction} task - An async function to retry. + * Invoked with (callback). * @param {Function} [callback] - An optional callback which is called when the * task has succeeded, or after the final failed attempt. It receives the `err` * and `result` arguments of the last attempt at completing the `task`. Invoked * with (err, results). + * * @example * * // The `retry` function can be used as a stand-alone control flow by passing @@ -82,7 +80,7 @@ import wrapAsync from './internal/wrapAsync'; * // individual methods that are not as reliable, like this: * async.auto({ * users: api.getUsers.bind(api), - * payments: async.retry(3, api.getPayments.bind(api)) + * payments: async.retryable(3, api.getPayments.bind(api)) * }, function(err, results) { * // do something with the results * }); diff --git a/lib/retryable.js b/lib/retryable.js index 6fba8e234..57410317a 100644 --- a/lib/retryable.js +++ b/lib/retryable.js @@ -3,8 +3,9 @@ import initialParams from './internal/initialParams'; import wrapAsync from './internal/wrapAsync'; /** - * A close relative of [`retry`]{@link module:ControlFlow.retry}. This method wraps a task and makes it - * retryable, rather than immediately calling it with retries. + * A close relative of [`retry`]{@link module:ControlFlow.retry}. This method + * wraps a task and makes it retryable, rather than immediately calling it + * with retries. * * @name retryable * @static @@ -14,9 +15,12 @@ import wrapAsync from './internal/wrapAsync'; * @category Control Flow * @param {Object|number} [opts = {times: 5, interval: 0}| 5] - optional * options, exactly the same as from `retry` - * @param {Function} task - the asynchronous function to wrap - * @returns {Functions} The wrapped function, which when invoked, will retry on - * an error, based on the parameters specified in `opts`. + * @param {AsyncFunction} task - the asynchronous function to wrap. + * This function will be passed any arguments passed to the returned wrapper. + * Invoked with (...args, callback). + * @returns {AsyncFunction} The wrapped function, which when invoked, will + * retry on an error, based on the parameters specified in `opts`. + * This function will accept the same parameters as `task`. * @example * * async.auto({ diff --git a/lib/seq.js b/lib/seq.js index 9a3584718..4fceceb60 100644 --- a/lib/seq.js +++ b/lib/seq.js @@ -17,7 +17,7 @@ import arrayMap from 'lodash/_arrayMap'; * @method * @see [async.compose]{@link module:ControlFlow.compose} * @category Control Flow - * @param {...Function} functions - the asynchronous functions to compose + * @param {...AsyncFunction} functions - the asynchronous functions to compose * @returns {Function} a function that composes the `functions` in order * @example * diff --git a/lib/series.js b/lib/series.js index 7955cf251..eef153411 100644 --- a/lib/series.js +++ b/lib/series.js @@ -27,9 +27,9 @@ import eachOfSeries from './eachOfSeries'; * @memberOf module:ControlFlow * @method * @category Control Flow - * @param {Array|Iterable|Object} tasks - A collection containing functions to run, each - * function is passed a `callback(err, result)` it must call on completion with - * an error `err` (which can be `null`) and an optional `result` value. + * @param {Array|Iterable|Object} tasks - A collection containing + * [async functions]{@link AsyncFunction} to run in series. + * Each function can complete with any number of optional `result` values. * @param {Function} [callback] - An optional callback to run once all the * functions have completed. This function gets a results array (or object) * containing all the result arguments passed to the `task` callbacks. Invoked diff --git a/lib/someLimit.js b/lib/someLimit.js index bf4c601cb..161c3cfe2 100644 --- a/lib/someLimit.js +++ b/lib/someLimit.js @@ -14,10 +14,10 @@ import identity from 'lodash/identity'; * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. * @param {number} limit - The maximum number of async operations at a time. - * @param {Function} iteratee - A truth test to apply to each item in the array - * in parallel. The iteratee is passed a `callback(err, truthValue)` which must - * be called with a boolean argument once it has completed. Invoked with - * (item, callback). + * @param {AsyncFunction} iteratee - An async truth test to apply to each item + * in the collections in parallel. + * The iteratee should complete with a boolean `result` value. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called as soon as any * iteratee returns `true`, or after all the iteratee functions have finished. * Result will be either `true` or `false` depending on the values of the async diff --git a/lib/someSeries.js b/lib/someSeries.js index 8357a8b16..8e278deb7 100644 --- a/lib/someSeries.js +++ b/lib/someSeries.js @@ -12,10 +12,10 @@ import doLimit from './internal/doLimit'; * @alias anySeries * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A truth test to apply to each item in the array - * in parallel. The iteratee is passed a `callback(err, truthValue)` which must - * be called with a boolean argument once it has completed. Invoked with - * (item, callback). + * @param {AsyncFunction} iteratee - An async truth test to apply to each item + * in the collections in series. + * The iteratee should complete with a boolean `result` value. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called as soon as any * iteratee returns `true`, or after all the iteratee functions have finished. * Result will be either `true` or `false` depending on the values of the async diff --git a/lib/sortBy.js b/lib/sortBy.js index 812a39ef3..399debd23 100644 --- a/lib/sortBy.js +++ b/lib/sortBy.js @@ -14,10 +14,11 @@ import wrapAsync from './internal/wrapAsync'; * @method * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A function to apply to each item in `coll`. - * The iteratee is passed a `callback(err, sortValue)` which must be called once - * it has completed with an error (which can be `null`) and a value to use as - * the sort criteria. Invoked with (item, callback). + * @param {AsyncFunction} iteratee - An async function to apply to each item in + * `coll`. + * The iteratee should complete with a value to use as the sort criteria as + * its `result`. + * Invoked with (item, callback). * @param {Function} callback - A callback which is called after all the * `iteratee` functions have finished, or an error occurs. Results is the items * from the original `coll` sorted by the values returned by the `iteratee` diff --git a/lib/timeout.js b/lib/timeout.js index ad050e01a..475b20889 100644 --- a/lib/timeout.js +++ b/lib/timeout.js @@ -11,14 +11,13 @@ import wrapAsync from './internal/wrapAsync'; * @memberOf module:Utils * @method * @category Util - * @param {Function} asyncFn - The asynchronous function you want to set the - * time limit. + * @param {AsyncFunction} asyncFn - The async function to limit in time. * @param {number} milliseconds - The specified time limit. * @param {*} [info] - Any variable you want attached (`string`, `object`, etc) * to timeout Error for more information.. - * @returns {Function} Returns a wrapped function that can be used with any of - * the control flow functions. Invoke this function with the same - * parameters as you would `asyncFunc`. + * @returns {AsyncFunction} Returns a wrapped function that can be used with any + * of the control flow functions. + * Invoke this function with the same parameters as you would `asyncFunc`. * @example * * function myFunction(foo, callback) { diff --git a/lib/times.js b/lib/times.js index bdd2e6b34..ebd336c4c 100644 --- a/lib/times.js +++ b/lib/times.js @@ -12,8 +12,8 @@ import doLimit from './internal/doLimit'; * @see [async.map]{@link module:Collections.map} * @category Control Flow * @param {number} n - The number of times to run the function. - * @param {Function} iteratee - The function to call `n` times. Invoked with the - * iteration index and a callback (n, next). + * @param {AsyncFunction} iteratee - The async function to call `n` times. + * Invoked with the iteration index and a callback: (n, next). * @param {Function} callback - see {@link module:Collections.map}. * @example * diff --git a/lib/timesLimit.js b/lib/timesLimit.js index 7ba91d69c..22be265a1 100644 --- a/lib/timesLimit.js +++ b/lib/timesLimit.js @@ -14,8 +14,8 @@ import wrapAsync from './internal/wrapAsync'; * @category Control Flow * @param {number} count - The number of times to run the function. * @param {number} limit - The maximum number of async operations at a time. - * @param {Function} iteratee - The function to call `n` times. Invoked with the - * iteration index and a callback (n, next). + * @param {AsyncFunction} iteratee - The async function to call `n` times. + * Invoked with the iteration index and a callback: (n, next). * @param {Function} callback - see [async.map]{@link module:Collections.map}. */ export default function timeLimit(count, limit, iteratee, callback) { diff --git a/lib/timesSeries.js b/lib/timesSeries.js index 672428d18..17ec0ca85 100644 --- a/lib/timesSeries.js +++ b/lib/timesSeries.js @@ -11,8 +11,8 @@ import doLimit from './internal/doLimit'; * @see [async.times]{@link module:ControlFlow.times} * @category Control Flow * @param {number} n - The number of times to run the function. - * @param {Function} iteratee - The function to call `n` times. Invoked with the - * iteration index and a callback (n, next). + * @param {AsyncFunction} iteratee - The async function to call `n` times. + * Invoked with the iteration index and a callback: (n, next). * @param {Function} callback - see {@link module:Collections.map}. */ export default doLimit(timesLimit, 1); diff --git a/lib/transform.js b/lib/transform.js index d3c1d6a03..ef254a136 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -18,11 +18,8 @@ import wrapAsync from './internal/wrapAsync'; * @param {Array|Iterable|Object} coll - A collection to iterate over. * @param {*} [accumulator] - The initial state of the transform. If omitted, * it will default to an empty Object or Array, depending on the type of `coll` - * @param {Function} iteratee - A function applied to each item in the - * collection that potentially modifies the accumulator. The `iteratee` is - * passed a `callback(err)` which accepts an optional error as its first - * argument. If an error is passed to the callback, the transform is stopped - * and the main `callback` is immediately called with the error. + * @param {AsyncFunction} iteratee - A function applied to each item in the + * collection that potentially modifies the accumulator. * Invoked with (accumulator, item, key, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Result is the transformed accumulator. diff --git a/lib/unmemoize.js b/lib/unmemoize.js index 4b2db4dc8..d89075f5d 100644 --- a/lib/unmemoize.js +++ b/lib/unmemoize.js @@ -8,8 +8,8 @@ * @method * @see [async.memoize]{@link module:Utils.memoize} * @category Util - * @param {Function} fn - the memoized function - * @returns {Function} a function that calls the original unmemoized function + * @param {AsyncFunction} fn - the memoized function + * @returns {AsyncFunction} a function that calls the original unmemoized function */ export default function unmemoize(fn) { return function () { diff --git a/lib/whilst.js b/lib/whilst.js index 8135af7d9..dc56a8a24 100644 --- a/lib/whilst.js +++ b/lib/whilst.js @@ -15,9 +15,8 @@ import wrapAsync from './internal/wrapAsync'; * @category Control Flow * @param {Function} test - synchronous truth test to perform before each * execution of `iteratee`. Invoked with (). - * @param {Function} iteratee - A function which is called each time `test` passes. - * The function is passed a `callback(err)`, which must be called once it has - * completed with an optional `err` argument. Invoked with (callback). + * @param {AsyncFunction} fn - An async function which is called each time + * `test` passes. Invoked with (callback). * @param {Function} [callback] - A callback which is called after the test * function has failed and repeated execution of `iteratee` has stopped. `callback` * will be passed an error and any arguments passed to the final `iteratee`'s From fab1e45a4a61659049eb43460a1b6183d7429c4d Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Sat, 1 Apr 2017 16:12:45 -0700 Subject: [PATCH 15/16] fix lint error --- lib/until.js | 19 +++++++++---------- lib/whilst.js | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/until.js b/lib/until.js index 7b0b0b5ec..942a38120 100644 --- a/lib/until.js +++ b/lib/until.js @@ -1,9 +1,9 @@ import whilst from './whilst'; /** - * Repeatedly call `fn` until `test` returns `true`. Calls `callback` when + * Repeatedly call `iteratee` until `test` returns `true`. Calls `callback` when * stopped, or an error occurs. `callback` will be passed an error and any - * arguments passed to the final `fn`'s callback. + * arguments passed to the final `iteratee`'s callback. * * The inverse of [whilst]{@link module:ControlFlow.whilst}. * @@ -14,17 +14,16 @@ import whilst from './whilst'; * @see [async.whilst]{@link module:ControlFlow.whilst} * @category Control Flow * @param {Function} test - synchronous truth test to perform before each - * execution of `fn`. Invoked with (). - * @param {Function} fn - A function which is called each time `test` fails. - * The function is passed a `callback(err)`, which must be called once it has - * completed with an optional `err` argument. Invoked with (callback). + * execution of `iteratee`. Invoked with (). + * @param {AsyncFunction} iteratee - An async function which is called each time + * `test` fails. Invoked with (callback). * @param {Function} [callback] - A callback which is called after the test - * function has passed and repeated execution of `fn` has stopped. `callback` - * will be passed an error and any arguments passed to the final `fn`'s + * function has passed and repeated execution of `iteratee` has stopped. `callback` + * will be passed an error and any arguments passed to the final `iteratee`'s * callback. Invoked with (err, [results]); */ -export default function until(test, fn, callback) { +export default function until(test, iteratee, callback) { whilst(function() { return !test.apply(this, arguments); - }, fn, callback); + }, iteratee, callback); } diff --git a/lib/whilst.js b/lib/whilst.js index dc56a8a24..57edad615 100644 --- a/lib/whilst.js +++ b/lib/whilst.js @@ -15,7 +15,7 @@ import wrapAsync from './internal/wrapAsync'; * @category Control Flow * @param {Function} test - synchronous truth test to perform before each * execution of `iteratee`. Invoked with (). - * @param {AsyncFunction} fn - An async function which is called each time + * @param {AsyncFunction} iteratee - An async function which is called each time * `test` passes. Invoked with (callback). * @param {Function} [callback] - A callback which is called after the test * function has failed and repeated execution of `iteratee` has stopped. `callback` From 8faed87d71496776c7100dc29f2a8e294261c820 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Sun, 2 Apr 2017 13:49:47 -0700 Subject: [PATCH 16/16] missed some docs --- lib/some.js | 8 ++++---- lib/waterfall.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/some.js b/lib/some.js index 3cb77e89c..ce91ce884 100644 --- a/lib/some.js +++ b/lib/some.js @@ -14,10 +14,10 @@ import identity from 'lodash/identity'; * @alias any * @category Collection * @param {Array|Iterable|Object} coll - A collection to iterate over. - * @param {Function} iteratee - A truth test to apply to each item in the array - * in parallel. The iteratee is passed a `callback(err, truthValue)` which must - * be called with a boolean argument once it has completed. Invoked with - * (item, callback). + * @param {AsyncFunction} iteratee - An async truth test to apply to each item + * in the collections in parallel. + * The iteratee should complete with a boolean `result` value. + * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called as soon as any * iteratee returns `true`, or after all the iteratee functions have finished. * Result will be either `true` or `false` depending on the values of the async diff --git a/lib/waterfall.js b/lib/waterfall.js index cb2c1fd02..24befac96 100644 --- a/lib/waterfall.js +++ b/lib/waterfall.js @@ -17,10 +17,10 @@ import wrapAsync from './internal/wrapAsync'; * @memberOf module:ControlFlow * @method * @category Control Flow - * @param {Array} tasks - An array of functions to run, each function is passed - * a `callback(err, result1, result2, ...)` it must call on completion. The - * first argument is an error (which can be `null`) and any further arguments - * will be passed as arguments in order to the next task. + * @param {Array} tasks - An array of [async functions]{@link AsyncFunction} + * to run. + * Each function should complete with any number of `result` values. + * The `result` values will be passed as arguments, in order, to the next task. * @param {Function} [callback] - An optional callback to run once all the * functions have completed. This will be passed the results of the last task's * callback. Invoked with (err, [results]).