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..6732369c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,12 +4,14 @@ node_js: - "0.10" - "0.12" - "4" + - "6" + - "7" matrix: include: - - node_js: "6" + - node_js: "7" addons: - firefox: "49.0" + firefox: "52.0" env: BROWSER=true MAKE_TEST=true env: matrix: BROWSER=false MAKE_TEST=false @@ -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/dist/async.js b/dist/async.js index 155d77977..6e4ef324e 100644 --- a/dist/async.js +++ b/dist/async.js @@ -887,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. @@ -910,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) { @@ -988,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) { @@ -1002,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); }); @@ -1205,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. @@ -3083,7 +3107,7 @@ function _withoutIndex(iteratee) { * }); */ function eachLimit(coll, iteratee, callback) { - eachOf(coll, _withoutIndex(iteratee), callback); + eachOf(coll, _withoutIndex(wrapAsync$1(iteratee)), callback); } /** @@ -3108,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); } /** @@ -3318,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); } /** 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 dec39d239..96eea543b 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 @@ -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 @@ -48,7 +48,8 @@ import initialParams from './internal/initialParams'; * } * ], callback); * - * // es6 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/auto.js b/lib/auto.js index fe98e0659..ba0fdc337 100644 --- a/lib/auto.js +++ b/lib/auto.js @@ -8,19 +8,20 @@ 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 + * 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. * @@ -30,7 +31,7 @@ import onlyOnce from './internal/onlyOnce'; * @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: @@ -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..bc8ddd6ef 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; @@ -38,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. @@ -106,22 +108,27 @@ 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 (taskFn.length === 1) { + } else if (hasNoDeps) { // 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 +138,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/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 b198d1720..7b52c22c2 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 @@ -13,10 +14,9 @@ import onlyOnce from './internal/onlyOnce'; * @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 @@ -25,17 +25,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/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 d9222aa3d..4407305f6 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 @@ -15,11 +16,10 @@ import onlyOnce from './internal/onlyOnce'; * @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. @@ -28,10 +28,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..2f91dd5a8 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 @@ -13,11 +14,10 @@ import onlyOnce from './internal/onlyOnce'; * @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`. @@ -40,17 +40,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/each.js b/lib/each.js index 5bf6bd4d6..29f2bafc8 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. @@ -18,12 +19,10 @@ import withoutIndex from './internal/withoutIndex'; * @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 @@ -61,5 +60,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..462e329ee 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. @@ -13,15 +14,14 @@ import withoutIndex from './internal/withoutIndex'; * @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). */ 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..9ab3eb6d5 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) { @@ -45,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 @@ -76,5 +75,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..1de28efc6 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 @@ -13,15 +14,13 @@ import _eachOfLimit from './internal/eachOfLimit'; * @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). */ export default function eachOfLimit(coll, limit, iteratee, callback) { - _eachOfLimit(limit)(coll, iteratee, callback); + _eachOfLimit(limit)(coll, wrapAsync(iteratee), callback); } 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 c60d07e54..a579efca6 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 @@ -7,16 +8,17 @@ import initialParams from './internal/initialParams'; * 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 * @@ -36,6 +38,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/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 01473953c..f7be0b257 100644 --- a/lib/forever.js +++ b/lib/forever.js @@ -2,21 +2,22 @@ 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 * 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 @@ -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 = wrapAsync(ensureAsync(fn)); function next(err) { if (err) return done(err); 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 9fc71a0c3..b0a5ef339 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. * @@ -12,19 +12,19 @@ import mapLimit from './mapLimit'; * @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. */ 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/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 0bb7efe19..f1e8e988c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,11 +1,52 @@ +/** + * 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 should be called with a + * Error as its first argument to signal that an error occurred. + * 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", + * 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}. + * 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, 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 + * 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 +58,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'; diff --git a/lib/internal/applyEach.js b/lib/internal/applyEach.js index aca2b7d5b..285a6a25e 100644 --- a/lib/internal/applyEach.js +++ b/lib/internal/applyEach.js @@ -1,12 +1,13 @@ 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) { - fn.apply(that, args.concat(cb)); + wrapAsync(fn).apply(that, args.concat(cb)); }, callback); }); if (args.length) { 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/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/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/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 new file mode 100644 index 000000000..aee027504 --- /dev/null +++ b/lib/internal/wrapAsync.js @@ -0,0 +1,27 @@ +import identity from 'lodash/identity'; +import asyncify from '../asyncify'; + +var supportsSymbol = typeof Symbol === 'function'; + +function supportsAsync() { + var supported; + try { + /* eslint no-eval: 0 */ + supported = isAsync(eval('(async function () {})')); + } catch (e) { + supported = false; + } + return supported; +} + +function isAsync(fn) { + return supportsSymbol && fn[Symbol.toStringTag] === 'AsyncFunction'; +} + +function wrapAsync(asyncFn) { + return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn; +} + +export default supportsAsync() ? wrapAsync : identity; + +export { supportsAsync, isAsync }; 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 6c9fe06be..c6e020d03 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 @@ -15,10 +16,10 @@ import once from './internal/once'; * @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. @@ -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/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 ec836200f..de71c33e7 100644 --- a/lib/memoize.js +++ b/lib/memoize.js @@ -3,13 +3,14 @@ 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; } /** - * 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. * @@ -27,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) { @@ -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/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 5666843a5..906467c07 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. @@ -58,11 +59,9 @@ import queue from './internal/queue'; * @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. @@ -101,7 +100,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..9b4d6c380 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 @@ -13,9 +14,8 @@ import once from './internal/once'; * @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). @@ -44,6 +44,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/reduce.js b/lib/reduce.js index d2fe1c2fc..0d58c41e2 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 @@ -22,12 +23,12 @@ import once from './internal/once'; * @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). @@ -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/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 cc8d6b613..9e0613b48 100644 --- a/lib/reflect.js +++ b/lib/reflect.js @@ -1,18 +1,19 @@ 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 - * 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. @@ -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/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 a5ad866bf..bd60644ce 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 @@ -12,6 +13,7 @@ import constant from 'lodash/constant'; * @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 @@ -26,16 +28,13 @@ import constant from 'lodash/constant'; * 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 @@ -81,7 +80,7 @@ import constant from 'lodash/constant'; * // 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 * }); @@ -124,9 +123,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..57410317a 100644 --- a/lib/retryable.js +++ b/lib/retryable.js @@ -1,9 +1,11 @@ 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 - * 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 @@ -13,9 +15,12 @@ import initialParams from './internal/initialParams'; * @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({ @@ -30,9 +35,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..4fceceb60 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 @@ -15,7 +17,7 @@ import reduce from './reduce'; * @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 * @@ -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/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/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/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 c82920027..399debd23 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 @@ -13,10 +14,11 @@ import map from './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, 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` @@ -50,8 +52,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/timeout.js b/lib/timeout.js index 93fe2e1c5..475b20889 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 @@ -10,14 +11,13 @@ import initialParams from './internal/initialParams'; * @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) { @@ -64,10 +64,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/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 68d5edee4..22be265a1 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 @@ -13,10 +14,11 @@ import range from 'lodash/_baseRange'; * @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) { - mapLimit(range(0, count, 1), limit, iteratee, callback); + var _iteratee = wrapAsync(iteratee); + mapLimit(range(0, count, 1), 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 f001c5192..ef254a136 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 @@ -17,11 +18,8 @@ import once from './internal/once'; * @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. @@ -56,9 +54,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/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/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/waterfall.js b/lib/waterfall.js index 7bc60daa9..24befac96 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 @@ -16,10 +17,10 @@ import onlyOnce from './internal/onlyOnce'; * @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]). @@ -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..57edad615 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 @@ -14,9 +15,8 @@ import onlyOnce from './internal/onlyOnce'; * @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} 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` * will be passed an error and any arguments passed to the final `iteratee`'s @@ -40,11 +40,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/asyncFunctions.js b/mocha_test/asyncFunctions.js new file mode 100644 index 000000000..b756a902f --- /dev/null +++ b/mocha_test/asyncFunctions.js @@ -0,0 +1,11 @@ +var supportsAsync = require('../lib/internal/wrapAsync').supportsAsync; + +describe('async function support', function () { + this.timeout(100); + + 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..ca76a56c9 --- /dev/null +++ b/mocha_test/es2017/asyncFunctions.js @@ -0,0 +1,682 @@ +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]; + const inputObj = {a: 1, b: 2, c: 3}; + + it('should asyncify async functions', (done) => { + async.asyncify(asyncIdentity)(42, (err, val) => { + assert(val === 42); + done(err); + }) + }); + + it('should handle errors in async functions', (done) => { + async.asyncify(async function () { + throw new Error('thrown error') + })((err) => { + assert(err.message = 'thrown error'); + done(); + }) + }); + + /* + * Collections + */ + + 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 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); + 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); + }); + }); + + 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); + }); + }); + + /* + * 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 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) { + 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); + }) + }); + + /** + * 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(); + }) + }); +} diff --git a/package.json b/package.json index a7812b325..34dff0b6e 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",