diff --git a/lib/auto.js b/lib/auto.js index b06e3c5bd..ad92d74de 100644 --- a/lib/auto.js +++ b/lib/auto.js @@ -1,8 +1,7 @@ -import noop from './internal/noop'; - import once from './internal/once'; import onlyOnce from './internal/onlyOnce'; import wrapAsync from './internal/wrapAsync'; +import { promiseCallback, PROMISE_SYMBOL } from './internal/promiseCallback' /** * Determines the best order for running the {@link AsyncFunction}s in `tasks`, based on @@ -42,7 +41,7 @@ import wrapAsync from './internal/wrapAsync'; * pass an error to their callback. Results are always returned; however, if an * error occurs, no further `tasks` will be performed, and the results object * will only contain partial results. Invoked with (err, results). - * @returns undefined + * @returns {Promise} a promise, if a callback is not passed * @example * * async.auto({ @@ -83,13 +82,13 @@ import wrapAsync from './internal/wrapAsync'; * console.log('results = ', results); * }); */ -export default function (tasks, concurrency, callback) { - if (typeof concurrency === 'function') { +export default function auto(tasks, concurrency, callback) { + if (typeof concurrency !== 'number') { // concurrency is optional, shift the args. callback = concurrency; concurrency = null; } - callback = once(callback || noop); + callback = once(callback || promiseCallback()); var numTasks = Object.keys(tasks).length; if (!numTasks) { return callback(null); @@ -251,4 +250,6 @@ export default function (tasks, concurrency, callback) { }); return result; } + + return callback[PROMISE_SYMBOL] } diff --git a/lib/autoInject.js b/lib/autoInject.js index 004cfdadd..996fceb98 100644 --- a/lib/autoInject.js +++ b/lib/autoInject.js @@ -54,6 +54,7 @@ function parseParams(func) { * the tasks have been completed. It receives the `err` argument if any `tasks` * pass an error to their callback, and a `results` object with any completed * task results, similar to `auto`. + * @returns {Promise} a promise, if no callback is passed * @example * * // The example from `auto` can be rewritten as follows: @@ -142,5 +143,5 @@ export default function autoInject(tasks, callback) { } }); - auto(newTasks, callback); + return auto(newTasks, callback); } diff --git a/lib/compose.js b/lib/compose.js index 1706013b1..ea97c43a5 100644 --- a/lib/compose.js +++ b/lib/compose.js @@ -6,6 +6,9 @@ import seq from './seq'; * follows. Composing functions `f()`, `g()`, and `h()` would produce the result * of `f(g(h()))`, only this version uses callbacks to obtain the return values. * + * If the last argument to the composed function is not a function, a promise + * is returned when you call it. + * * Each function is executed with the `this` binding of the composed function. * * @name compose @@ -35,6 +38,6 @@ import seq from './seq'; * // result now equals 15 * }); */ -export default function(...args) { +export default function compose(...args) { return seq(...args.reverse()); } diff --git a/lib/concat.js b/lib/concat.js index 0a8251ae2..394e3c2bd 100644 --- a/lib/concat.js +++ b/lib/concat.js @@ -1,5 +1,5 @@ -import doLimit from './internal/doLimit'; import concatLimit from './concatLimit'; +import awaitify from './internal/awaitify' /** * Applies `iteratee` to each item in `coll`, concatenating the results. Returns @@ -15,14 +15,18 @@ import concatLimit from './concatLimit'; * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over. * @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 + * @param {Function} [callback] - 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 * (err, results). + * @returns A Promise, if no callback is passed * @example * * async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files) { * // files is now a list of filenames that exist in the 3 directories * }); */ -export default doLimit(concatLimit, Infinity); +function concat(coll, iteratee, callback) { + return concatLimit(coll, Infinity, iteratee, callback) +} +export default awaitify(concat, 3); diff --git a/lib/concatLimit.js b/lib/concatLimit.js index 5dbbe8dd1..34c35df00 100644 --- a/lib/concatLimit.js +++ b/lib/concatLimit.js @@ -1,6 +1,6 @@ -import noop from './internal/noop'; import wrapAsync from './internal/wrapAsync'; import mapLimit from './mapLimit'; +import awaitify from './internal/awaitify' /** * The same as [`concat`]{@link module:Collections.concat} but runs a maximum of `limit` async operations at a time. @@ -19,11 +19,11 @@ import mapLimit from './mapLimit'; * `iteratee` functions have finished, or an error occurs. Results is an array * containing the concatenated results of the `iteratee` function. Invoked with * (err, results). + * @returns A Promise, if no callback is passed */ -export default function(coll, limit, iteratee, callback) { - callback = callback || noop; +function concatLimit(coll, limit, iteratee, callback) { var _iteratee = wrapAsync(iteratee); - mapLimit(coll, limit, (val, iterCb) => { + return mapLimit(coll, limit, (val, iterCb) => { _iteratee(val, (err, ...args) => { if (err) return iterCb(err); return iterCb(null, args); @@ -39,3 +39,4 @@ export default function(coll, limit, iteratee, callback) { return callback(err, result); }); } +export default awaitify(concatLimit, 4) diff --git a/lib/concatSeries.js b/lib/concatSeries.js index ae1bd67b6..0b3680365 100644 --- a/lib/concatSeries.js +++ b/lib/concatSeries.js @@ -1,5 +1,5 @@ -import doLimit from './internal/doLimit'; import concatLimit from './concatLimit'; +import awaitify from './internal/awaitify' /** * The same as [`concat`]{@link module:Collections.concat} but runs only a single async operation at a time. @@ -14,9 +14,13 @@ import concatLimit from './concatLimit'; * @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 + * @param {Function} [callback] - 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 * (err, results). + * @returns A Promise, if no callback is passed */ -export default doLimit(concatLimit, 1); +function concatSeries(coll, iteratee, callback) { + return concatLimit(coll, 1, iteratee, callback) +} +export default awaitify(concatSeries, 3); diff --git a/lib/detect.js b/lib/detect.js index 3e28f0ee2..4a6746a5d 100644 --- a/lib/detect.js +++ b/lib/detect.js @@ -1,5 +1,6 @@ import createTester from './internal/createTester'; -import doParallel from './internal/doParallel'; +import eachOf from './eachOf' +import awaitify from './internal/awaitify' /** * Returns the first value in `coll` that passes an async truth test. The @@ -26,6 +27,7 @@ import doParallel from './internal/doParallel'; * Result will be the first item in the array that passes the truth test * (iteratee) or the value `undefined` if none passed. Invoked with * (err, result). + * @returns A Promise, if no callback is passed * @example * * async.detect(['file1','file2','file3'], function(filePath, callback) { @@ -36,4 +38,7 @@ import doParallel from './internal/doParallel'; * // result now equals the first file in the list that exists * }); */ -export default doParallel(createTester(bool => bool, (res, item) => item)); +function detect(coll, iteratee, callback) { + return createTester(bool => bool, (res, item) => item)(eachOf, coll, iteratee, callback) +} +export default awaitify(detect, 3) diff --git a/lib/detectLimit.js b/lib/detectLimit.js index a0f9d8936..7630d6d0c 100644 --- a/lib/detectLimit.js +++ b/lib/detectLimit.js @@ -1,5 +1,6 @@ import createTester from './internal/createTester'; -import doParallelLimit from './internal/doParallelLimit'; +import eachOfLimit from './internal/eachOfLimit' +import awaitify from './internal/awaitify' /** * The same as [`detect`]{@link module:Collections.detect} but runs a maximum of `limit` async operations at a @@ -22,5 +23,9 @@ import doParallelLimit from './internal/doParallelLimit'; * Result will be the first item in the array that passes the truth test * (iteratee) or the value `undefined` if none passed. Invoked with * (err, result). + * @returns a Promise if no callback is passed */ -export default doParallelLimit(createTester(bool => bool, (res, item) => item)); +function detectLimit(coll, limit, iteratee, callback) { + return createTester(bool => bool, (res, item) => item)(eachOfLimit(limit), coll, iteratee, callback) +} +export default awaitify(detectLimit, 4) diff --git a/lib/detectSeries.js b/lib/detectSeries.js index f563eae40..e85c4f8d0 100644 --- a/lib/detectSeries.js +++ b/lib/detectSeries.js @@ -1,5 +1,6 @@ -import detectLimit from './detectLimit'; -import doLimit from './internal/doLimit'; +import createTester from './internal/createTester' +import eachOfLimit from './internal/eachOfLimit' +import awaitify from './internal/awaitify' /** * The same as [`detect`]{@link module:Collections.detect} but runs only a single async operation at a time. @@ -20,5 +21,10 @@ import doLimit from './internal/doLimit'; * Result will be the first item in the array that passes the truth test * (iteratee) or the value `undefined` if none passed. Invoked with * (err, result). + * @returns a Promise if no callback is passed */ -export default doLimit(detectLimit, 1); +function detectSeries(coll, iteratee, callback) { + return createTester(bool => bool, (res, item) => item)(eachOfLimit(1), coll, iteratee, callback) +} + +export default awaitify(detectSeries, 3) diff --git a/lib/doUntil.js b/lib/doUntil.js index d2e5b0431..a484df6be 100644 --- a/lib/doUntil.js +++ b/lib/doUntil.js @@ -20,10 +20,11 @@ import wrapAsync from './internal/wrapAsync'; * 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]); + * @returns {Promise} a promise, if no callback is passed */ export default function doUntil(iteratee, test, callback) { const _test = wrapAsync(test) - doWhilst(iteratee, (...args) => { + return doWhilst(iteratee, (...args) => { const cb = args.pop() _test(...args, (err, truth) => cb (err, !truth)) }, callback); diff --git a/lib/doWhilst.js b/lib/doWhilst.js index b3d52a520..3d9a1afc4 100644 --- a/lib/doWhilst.js +++ b/lib/doWhilst.js @@ -1,7 +1,6 @@ -import noop from './internal/noop'; - import onlyOnce from './internal/onlyOnce'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify' /** * The post-check version of [`whilst`]{@link module:ControlFlow.whilst}. To reflect the difference in @@ -24,10 +23,10 @@ import wrapAsync from './internal/wrapAsync'; * 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 callback. Invoked with (err, [results]); - * @return undefined + * @returns {Promise} a promise, if no callback is passed */ -export default function doWhilst(iteratee, test, callback) { - callback = onlyOnce(callback || noop); +function doWhilst(iteratee, test, callback) { + callback = onlyOnce(callback); var _fn = wrapAsync(iteratee); var _test = wrapAsync(test); var results @@ -48,3 +47,5 @@ export default function doWhilst(iteratee, test, callback) { return check(null, true); } + +export default awaitify(doWhilst, 3) diff --git a/lib/each.js b/lib/each.js index dc856ac54..7f6e689c5 100644 --- a/lib/each.js +++ b/lib/each.js @@ -1,6 +1,7 @@ import eachOf from './eachOf'; import withoutIndex from './internal/withoutIndex'; import wrapAsync from './internal/wrapAsync' +import awaitify from './internal/awaitify' /** * Applies the function `iteratee` to each item in `coll`, in parallel. @@ -25,6 +26,7 @@ import wrapAsync from './internal/wrapAsync' * 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). + * @returns {Promise} a promise, if a callback is omitted * @example * * // assuming openFiles is an array of file names and saveFile is a function @@ -59,6 +61,8 @@ import wrapAsync from './internal/wrapAsync' * } * }); */ -export default function eachLimit(coll, iteratee, callback) { - eachOf(coll, withoutIndex(wrapAsync(iteratee)), callback); +function eachLimit(coll, iteratee, callback) { + return eachOf(coll, withoutIndex(wrapAsync(iteratee)), callback); } + +export default awaitify(eachLimit, 3) diff --git a/lib/eachLimit.js b/lib/eachLimit.js index e4d67cbf2..5c491e841 100644 --- a/lib/eachLimit.js +++ b/lib/eachLimit.js @@ -1,6 +1,7 @@ import eachOfLimit from './internal/eachOfLimit'; import withoutIndex from './internal/withoutIndex'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify' /** * The same as [`each`]{@link module:Collections.each} but runs a maximum of `limit` async operations at a time. @@ -21,7 +22,9 @@ import wrapAsync from './internal/wrapAsync'; * 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). + * @returns {Promise} a promise, if a callback is omitted */ -export default function eachLimit(coll, limit, iteratee, callback) { - eachOfLimit(limit)(coll, withoutIndex(wrapAsync(iteratee)), callback); +function eachLimit(coll, limit, iteratee, callback) { + return eachOfLimit(limit)(coll, withoutIndex(wrapAsync(iteratee)), callback); } +export default awaitify(eachLimit, 4) diff --git a/lib/eachOf.js b/lib/eachOf.js index 070204bf0..0b847aec9 100644 --- a/lib/eachOf.js +++ b/lib/eachOf.js @@ -1,15 +1,14 @@ import isArrayLike from './internal/isArrayLike'; import breakLoop from './internal/breakLoop'; import eachOfLimit from './eachOfLimit'; -import doLimit from './internal/doLimit'; -import noop from './internal/noop'; import once from './internal/once'; import onlyOnce from './internal/onlyOnce'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify' // eachOf implementation optimized for array-likes function eachOfArrayLike(coll, iteratee, callback) { - callback = once(callback || noop); + callback = once(callback); var index = 0, completed = 0, {length} = coll, @@ -36,7 +35,9 @@ function eachOfArrayLike(coll, iteratee, callback) { } // a generic version of eachOf which can handle array, object, and iterator cases. -var eachOfGeneric = doLimit(eachOfLimit, Infinity); +function eachOfGeneric (coll, iteratee, callback) { + return eachOfLimit(coll, Infinity, iteratee, callback); +} /** * Like [`each`]{@link module:Collections.each}, except that it passes the key (or index) as the second argument @@ -56,6 +57,7 @@ var eachOfGeneric = doLimit(eachOfLimit, Infinity); * Invoked with (item, key, callback). * @param {Function} [callback] - A callback which is called when all * `iteratee` functions have finished, or an error occurs. Invoked with (err). + * @returns {Promise} a promise, if a callback is omitted * @example * * var obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"}; @@ -77,7 +79,9 @@ var eachOfGeneric = doLimit(eachOfLimit, Infinity); * doSomethingWith(configs); * }); */ -export default function(coll, iteratee, callback) { +function eachOf(coll, iteratee, callback) { var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric; - eachOfImplementation(coll, wrapAsync(iteratee), callback); + return eachOfImplementation(coll, wrapAsync(iteratee), callback); } + +export default awaitify(eachOf, 3) diff --git a/lib/eachOfLimit.js b/lib/eachOfLimit.js index f439be675..f65d7d187 100644 --- a/lib/eachOfLimit.js +++ b/lib/eachOfLimit.js @@ -1,5 +1,6 @@ import _eachOfLimit from './internal/eachOfLimit'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify' /** * The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a @@ -20,7 +21,10 @@ import wrapAsync from './internal/wrapAsync'; * Invoked with (item, key, callback). * @param {Function} [callback] - A callback which is called when all * `iteratee` functions have finished, or an error occurs. Invoked with (err). + * @returns {Promise} a promise, if a callback is omitted */ -export default function eachOfLimit(coll, limit, iteratee, callback) { - _eachOfLimit(limit)(coll, wrapAsync(iteratee), callback); +function eachOfLimit(coll, limit, iteratee, callback) { + return _eachOfLimit(limit)(coll, wrapAsync(iteratee), callback); } + +export default awaitify(eachOfLimit, 4) diff --git a/lib/eachOfSeries.js b/lib/eachOfSeries.js index d64e7ec24..c48a66b8b 100644 --- a/lib/eachOfSeries.js +++ b/lib/eachOfSeries.js @@ -1,5 +1,5 @@ import eachOfLimit from './eachOfLimit'; -import doLimit from './internal/doLimit'; +import awaitify from './internal/awaitify' /** * The same as [`eachOf`]{@link module:Collections.eachOf} but runs only a single async operation at a time. @@ -17,5 +17,9 @@ import doLimit from './internal/doLimit'; * Invoked with (item, key, callback). * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. Invoked with (err). + * @returns {Promise} a promise, if a callback is omitted */ -export default doLimit(eachOfLimit, 1); +function eachOfSeries(coll, iteratee, callback) { + return eachOfLimit(coll, 1, iteratee, callback) +} +export default awaitify(eachOfSeries, 3); diff --git a/lib/eachSeries.js b/lib/eachSeries.js index ae6f6c70e..b4a86ba16 100644 --- a/lib/eachSeries.js +++ b/lib/eachSeries.js @@ -1,5 +1,5 @@ import eachLimit from './eachLimit'; -import doLimit from './internal/doLimit'; +import awaitify from './internal/awaitify' /** * The same as [`each`]{@link module:Collections.each} but runs only a single async operation at a time. @@ -19,5 +19,9 @@ import doLimit from './internal/doLimit'; * 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). + * @returns {Promise} a promise, if a callback is omitted */ -export default doLimit(eachLimit, 1); +function eachSeries(coll, iteratee, callback) { + return eachLimit(coll, 1, iteratee, callback) +} +export default awaitify(eachSeries, 3); diff --git a/lib/every.js b/lib/every.js index be2bc4aa2..a5ce971cc 100644 --- a/lib/every.js +++ b/lib/every.js @@ -1,5 +1,6 @@ import createTester from './internal/createTester'; -import doParallel from './internal/doParallel'; +import eachOf from './eachOf' +import awaitify from './internal/awaitify' /** * Returns `true` if every element in `coll` satisfies an async test. If any @@ -19,6 +20,7 @@ import doParallel from './internal/doParallel'; * @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). + * @returns {Promise} a promise, if no callback provided * @example * * async.every(['file1','file2','file3'], function(filePath, callback) { @@ -29,4 +31,7 @@ import doParallel from './internal/doParallel'; * // if result is true then every file exists * }); */ -export default doParallel(createTester(bool => !bool, res => !res)); +function every(coll, iteratee, callback) { + return createTester(bool => !bool, res => !res)(eachOf, coll, iteratee, callback) +} +export default awaitify(every, 3); diff --git a/lib/everyLimit.js b/lib/everyLimit.js index 78ffa36ea..787c704d9 100644 --- a/lib/everyLimit.js +++ b/lib/everyLimit.js @@ -1,5 +1,6 @@ import createTester from './internal/createTester'; -import doParallelLimit from './internal/doParallelLimit'; +import eachOfLimit from './internal/eachOfLimit' +import awaitify from './internal/awaitify' /** * The same as [`every`]{@link module:Collections.every} but runs a maximum of `limit` async operations at a time. @@ -20,5 +21,9 @@ import doParallelLimit from './internal/doParallelLimit'; * @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). + * @returns {Promise} a promise, if no callback provided */ -export default doParallelLimit(createTester(bool => !bool, res => !res)); +function everyLimit(coll, limit, iteratee, callback) { + return createTester(bool => !bool, res => !res)(eachOfLimit(limit), coll, iteratee, callback) +} +export default awaitify(everyLimit, 4); diff --git a/lib/everySeries.js b/lib/everySeries.js index c450bff40..07d261266 100644 --- a/lib/everySeries.js +++ b/lib/everySeries.js @@ -1,5 +1,6 @@ -import everyLimit from './everyLimit'; -import doLimit from './internal/doLimit'; +import createTester from './internal/createTester'; +import eachOfSeries from './eachOfSeries'; +import awaitify from './internal/awaitify'; /** * The same as [`every`]{@link module:Collections.every} but runs only a single async operation at a time. @@ -19,5 +20,9 @@ import doLimit from './internal/doLimit'; * @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). + * @returns {Promise} a promise, if no callback provided */ -export default doLimit(everyLimit, 1); +function everySeries(coll, iteratee, callback) { + return createTester(bool => !bool, res => !res)(eachOfSeries, coll, iteratee, callback) +} +export default awaitify(everySeries, 3); diff --git a/lib/filter.js b/lib/filter.js index 692802b61..b0f30292d 100644 --- a/lib/filter.js +++ b/lib/filter.js @@ -1,5 +1,6 @@ -import filter from './internal/filter'; -import doParallel from './internal/doParallel'; +import _filter from './internal/filter'; +import eachOf from './eachOf' +import awaitify from './internal/awaitify' /** * Returns a new array of all the values in `coll` which pass an async truth @@ -18,6 +19,7 @@ import doParallel from './internal/doParallel'; * with a boolean argument once it has completed. Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Invoked with (err, results). + * @returns {Promise} a promise, if no callback provided * @example * * async.filter(['file1','file2','file3'], function(filePath, callback) { @@ -28,4 +30,7 @@ import doParallel from './internal/doParallel'; * // results now equals an array of the existing files * }); */ -export default doParallel(filter); +function filter (coll, iteratee, callback) { + return _filter(eachOf, coll, iteratee, callback) +} +export default awaitify(filter, 3); diff --git a/lib/filterLimit.js b/lib/filterLimit.js index 946002de4..b421caa83 100644 --- a/lib/filterLimit.js +++ b/lib/filterLimit.js @@ -1,5 +1,6 @@ -import filter from './internal/filter'; -import doParallelLimit from './internal/doParallelLimit'; +import _filter from './internal/filter'; +import eachOfLimit from './internal/eachOfLimit' +import awaitify from './internal/awaitify' /** * The same as [`filter`]{@link module:Collections.filter} but runs a maximum of `limit` async operations at a @@ -19,5 +20,9 @@ import doParallelLimit from './internal/doParallelLimit'; * with a boolean argument once it has completed. Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Invoked with (err, results). + * @returns {Promise} a promise, if no callback provided */ -export default doParallelLimit(filter); +function filterLimit (coll, limit, iteratee, callback) { + return _filter(eachOfLimit(limit), coll, iteratee, callback) +} +export default awaitify(filterLimit, 4); diff --git a/lib/filterSeries.js b/lib/filterSeries.js index 908afbcee..f53c3d842 100644 --- a/lib/filterSeries.js +++ b/lib/filterSeries.js @@ -1,5 +1,6 @@ -import filterLimit from './filterLimit'; -import doLimit from './internal/doLimit'; +import _filter from './internal/filter'; +import eachOfSeries from './eachOfSeries' +import awaitify from './internal/awaitify' /** * The same as [`filter`]{@link module:Collections.filter} but runs only a single async operation at a time. @@ -17,5 +18,9 @@ import doLimit from './internal/doLimit'; * with a boolean argument once it has completed. Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Invoked with (err, results) + * @returns {Promise} a promise, if no callback provided */ -export default doLimit(filterLimit, 1); +function filterSeries (coll, iteratee, callback) { + return _filter(eachOfSeries, coll, iteratee, callback) +} +export default awaitify(filterSeries, 3); diff --git a/lib/forever.js b/lib/forever.js index fb69adf0c..4914c21b7 100644 --- a/lib/forever.js +++ b/lib/forever.js @@ -1,7 +1,7 @@ -import noop from './internal/noop'; import onlyOnce from './internal/onlyOnce'; import ensureAsync from './ensureAsync'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify' /** * Calls the asynchronous function `fn` with a callback parameter that allows it @@ -19,6 +19,8 @@ import wrapAsync from './internal/wrapAsync'; * 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). + * @returns {Promise} a promise that rejects if an error occurs and an errback + * is not passed * @example * * async.forever( @@ -32,8 +34,8 @@ import wrapAsync from './internal/wrapAsync'; * } * ); */ -export default function forever(fn, errback) { - var done = onlyOnce(errback || noop); +function forever(fn, errback) { + var done = onlyOnce(errback); var task = wrapAsync(ensureAsync(fn)); function next(err) { @@ -41,5 +43,6 @@ export default function forever(fn, errback) { if (err === false) return; task(next); } - next(); + return next(); } +export default awaitify(forever, 2) diff --git a/lib/groupBy.js b/lib/groupBy.js index 860a01f80..299336722 100644 --- a/lib/groupBy.js +++ b/lib/groupBy.js @@ -1,4 +1,3 @@ -import doLimit from './internal/doLimit'; import groupByLimit from './groupByLimit'; /** @@ -25,6 +24,7 @@ import groupByLimit from './groupByLimit'; * @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. + * @returns {Promise} a promise, if no callback is passed * @example * * async.groupBy(['userId1', 'userId2', 'userId3'], function(userId, callback) { @@ -37,4 +37,6 @@ import groupByLimit from './groupByLimit'; * // e.g. { 30: ['userId1', 'userId3'], 42: ['userId2']}; * }); */ -export default doLimit(groupByLimit, Infinity); +export default function groupBy (coll, iteratee, callback) { + return groupByLimit(coll, Infinity, iteratee, callback) +} diff --git a/lib/groupByLimit.js b/lib/groupByLimit.js index f0402aa47..24d5c409b 100644 --- a/lib/groupByLimit.js +++ b/lib/groupByLimit.js @@ -1,6 +1,7 @@ -import noop from './internal/noop'; import mapLimit from './mapLimit'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify' + /** * The same as [`groupBy`]{@link module:Collections.groupBy} but runs a maximum of `limit` async operations at a time. * @@ -19,11 +20,11 @@ import wrapAsync from './internal/wrapAsync'; * @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. + * @returns {Promise} a promise, if no callback is passed */ -export default function(coll, limit, iteratee, callback) { - callback = callback || noop; +function groupByLimit(coll, limit, iteratee, callback) { var _iteratee = wrapAsync(iteratee); - mapLimit(coll, limit, (val, iterCb) => { + return mapLimit(coll, limit, (val, iterCb) => { _iteratee(val, (err, key) => { if (err) return iterCb(err); return iterCb(null, {key, val}); @@ -49,3 +50,5 @@ export default function(coll, limit, iteratee, callback) { return callback(err, result); }); } + +export default awaitify(groupByLimit, 4); diff --git a/lib/groupBySeries.js b/lib/groupBySeries.js index a093d7fa7..ee673e1c9 100644 --- a/lib/groupBySeries.js +++ b/lib/groupBySeries.js @@ -1,4 +1,3 @@ -import doLimit from './internal/doLimit'; import groupByLimit from './groupByLimit'; /** @@ -11,7 +10,6 @@ import groupByLimit from './groupByLimit'; * @see [async.groupBy]{@link module:Collections.groupBy} * @category Collection * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over. - * @param {number} limit - The maximum number of async operations at a time. * @param {AsyncFunction} iteratee - An async function to apply to each item in * `coll`. * The iteratee should complete with a `key` to group the value under. @@ -19,5 +17,8 @@ import groupByLimit from './groupByLimit'; * @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. + * @returns {Promise} a promise, if no callback is passed */ -export default doLimit(groupByLimit, 1); +export default function groupBySeries (coll, iteratee, callback) { + return groupByLimit(coll, 1, iteratee, callback) +} diff --git a/lib/internal/awaitify.js b/lib/internal/awaitify.js new file mode 100644 index 000000000..57628de62 --- /dev/null +++ b/lib/internal/awaitify.js @@ -0,0 +1,24 @@ +// conditionally promisify a function. +// only return a promise if a callback is omitted +export default function awaitify (asyncFn, arity = asyncFn.length) { + if (!arity) throw new Error('arity is undefined') + function awaitable (...args) { + if (typeof args[arity - 1] === 'function') { + return asyncFn.apply(this, args) + } + + return new Promise((resolve, reject) => { + args[arity - 1] = (err, ...cbArgs) => { + if (err) return reject(err) + resolve(cbArgs.length > 1 ? cbArgs : cbArgs[0]) + } + asyncFn.apply(this, args) + }) + } + + Object.defineProperty(awaitable, 'name', { + value: `awaitable(${asyncFn.name})` + }) + + return awaitable +} diff --git a/lib/internal/createTester.js b/lib/internal/createTester.js index 4a5d39fcc..5858702ee 100644 --- a/lib/internal/createTester.js +++ b/lib/internal/createTester.js @@ -1,11 +1,11 @@ -import noop from './noop'; import breakLoop from './breakLoop'; +import wrapAsync from './wrapAsync' export default function _createTester(check, getResult) { - return (eachfn, arr, iteratee, cb) => { - cb = cb || noop; + return (eachfn, arr, _iteratee, cb) => { var testPassed = false; var testResult; + const iteratee = wrapAsync(_iteratee) eachfn(arr, (value, _, callback) => { iteratee(value, (err, result) => { if (err) return callback(err) diff --git a/lib/internal/doLimit.js b/lib/internal/doLimit.js deleted file mode 100644 index 70fb495d5..000000000 --- a/lib/internal/doLimit.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function doLimit(fn, limit) { - return (iterable, iteratee, cb) => fn(iterable, limit, iteratee, cb) -} diff --git a/lib/internal/doParallel.js b/lib/internal/doParallel.js deleted file mode 100644 index 2b76b6837..000000000 --- a/lib/internal/doParallel.js +++ /dev/null @@ -1,6 +0,0 @@ -import eachOf from '../eachOf'; -import wrapAsync from './wrapAsync'; - -export default function doParallel(fn) { - return (obj, iteratee, cb) => fn(eachOf, obj, wrapAsync(iteratee), cb); -} diff --git a/lib/internal/doParallelLimit.js b/lib/internal/doParallelLimit.js deleted file mode 100644 index 5e2703ae6..000000000 --- a/lib/internal/doParallelLimit.js +++ /dev/null @@ -1,6 +0,0 @@ -import eachOfLimit from './eachOfLimit'; -import wrapAsync from './wrapAsync'; - -export default function doParallelLimit(fn) { - return (obj, limit, iteratee, cb) => fn(eachOfLimit(limit), obj, wrapAsync(iteratee), cb); -} diff --git a/lib/internal/eachOfLimit.js b/lib/internal/eachOfLimit.js index 999cbfa5d..ec6c8beee 100644 --- a/lib/internal/eachOfLimit.js +++ b/lib/internal/eachOfLimit.js @@ -1,4 +1,3 @@ -import noop from './noop'; import once from './once'; import iterator from './iterator'; @@ -10,7 +9,7 @@ import breakLoop from './breakLoop'; export default (limit) => { return (obj, iteratee, callback) => { - callback = once(callback || noop); + callback = once(callback); if (limit <= 0) { throw new RangeError('concurrency limit cannot be less than 1') } diff --git a/lib/internal/filter.js b/lib/internal/filter.js index ab37390e8..15d7ae3e3 100644 --- a/lib/internal/filter.js +++ b/lib/internal/filter.js @@ -1,5 +1,4 @@ import isArrayLike from './isArrayLike'; -import noop from './noop'; import wrapAsync from './wrapAsync'; @@ -40,5 +39,5 @@ function filterGeneric(eachfn, coll, iteratee, callback) { export default function _filter(eachfn, coll, iteratee, callback) { var filter = isArrayLike(coll) ? filterArray : filterGeneric; - return filter(eachfn, coll, wrapAsync(iteratee), callback || noop); + return filter(eachfn, coll, wrapAsync(iteratee), callback); } diff --git a/lib/internal/initialParams.js b/lib/internal/initialParams.js index 4014785d3..9ea253a7c 100644 --- a/lib/internal/initialParams.js +++ b/lib/internal/initialParams.js @@ -1,6 +1,6 @@ export default function (fn) { return function (...args/*, callback*/) { var callback = args.pop(); - fn.call(this, args, callback); + return fn.call(this, args, callback); }; } diff --git a/lib/internal/map.js b/lib/internal/map.js index 3da8d9c8c..092f76d71 100644 --- a/lib/internal/map.js +++ b/lib/internal/map.js @@ -1,8 +1,6 @@ -import noop from './noop'; import wrapAsync from './wrapAsync'; export default function _asyncMap(eachfn, arr, iteratee, callback) { - callback = callback || noop; arr = arr || []; var results = []; var counter = 0; diff --git a/lib/internal/noop.js b/lib/internal/noop.js deleted file mode 100644 index ca6a74471..000000000 --- a/lib/internal/noop.js +++ /dev/null @@ -1 +0,0 @@ -export default function noop() {} diff --git a/lib/internal/once.js b/lib/internal/once.js index 1293d5e6b..10ab26b6c 100644 --- a/lib/internal/once.js +++ b/lib/internal/once.js @@ -1,8 +1,10 @@ export default function once(fn) { - return function (...args) { + function wrapper (...args) { if (fn === null) return; var callFn = fn; fn = null; callFn.apply(this, args); - }; + } + Object.assign(wrapper, fn) + return wrapper } diff --git a/lib/internal/parallel.js b/lib/internal/parallel.js index 8abf0d1e2..790b6e860 100644 --- a/lib/internal/parallel.js +++ b/lib/internal/parallel.js @@ -1,9 +1,8 @@ import isArrayLike from './isArrayLike'; -import noop from './noop'; import wrapAsync from './wrapAsync'; +import awaitify from './awaitify' -export default function _parallel(eachfn, tasks, callback) { - callback = callback || noop; +export default awaitify((eachfn, tasks, callback) => { var results = isArrayLike(tasks) ? [] : {}; eachfn(tasks, (task, key, taskCb) => { @@ -15,4 +14,4 @@ export default function _parallel(eachfn, tasks, callback) { taskCb(err); }); }, err => callback(err, results)); -} +}, 3) diff --git a/lib/internal/promiseCallback.js b/lib/internal/promiseCallback.js new file mode 100644 index 000000000..685dd7aa1 --- /dev/null +++ b/lib/internal/promiseCallback.js @@ -0,0 +1,19 @@ +const PROMISE_SYMBOL = Symbol('promiseCallback') + +function promiseCallback () { + let resolve, reject + function callback (err, ...args) { + if (err) return reject(err) + resolve(args.length > 1 ? args : args[0]) + } + + callback[PROMISE_SYMBOL] = new Promise((res, rej) => { + resolve = res, + reject = rej + }) + + return callback +} + + +export { promiseCallback, PROMISE_SYMBOL } diff --git a/lib/internal/queue.js b/lib/internal/queue.js index 4c1f5cc5f..751b56201 100644 --- a/lib/internal/queue.js +++ b/lib/internal/queue.js @@ -1,9 +1,10 @@ -import noop from './noop'; import onlyOnce from './onlyOnce'; import setImmediate from './setImmediate'; import DLL from './DoublyLinkedList'; import wrapAsync from './wrapAsync'; +const noop = () => {} + export default function queue(worker, concurrency, payload) { if (concurrency == null) { concurrency = 1; diff --git a/lib/internal/reject.js b/lib/internal/reject.js index 15226329f..d0549cc04 100644 --- a/lib/internal/reject.js +++ b/lib/internal/reject.js @@ -1,6 +1,8 @@ import filter from './filter'; +import wrapAsync from './wrapAsync' -export default function reject(eachfn, arr, iteratee, callback) { +export default function reject(eachfn, arr, _iteratee, callback) { + const iteratee = wrapAsync(_iteratee) return filter(eachfn, arr, (value, cb) => { iteratee(value, (err, v) => { cb(err, !v); diff --git a/lib/internal/wrapAsync.js b/lib/internal/wrapAsync.js index 5c3edd97b..270cf8fb6 100644 --- a/lib/internal/wrapAsync.js +++ b/lib/internal/wrapAsync.js @@ -13,6 +13,7 @@ function isAsyncIterable(obj) { } function wrapAsync(asyncFn) { + if (typeof asyncFn !== 'function') throw new Error('expected a function') return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn; } diff --git a/lib/map.js b/lib/map.js index 2512c327a..c32bd2dbc 100644 --- a/lib/map.js +++ b/lib/map.js @@ -1,5 +1,6 @@ -import doParallel from './internal/doParallel'; -import map from './internal/map'; +import _map from './internal/map'; +import eachOf from './eachOf' +import awaitify from './internal/awaitify' /** * Produces a new collection of values by mapping each value in `coll` through @@ -31,10 +32,14 @@ import map from './internal/map'; * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. Results is an Array of the * transformed items from the `coll`. Invoked with (err, results). + * @returns {Promise} a promise, if no callback is passed * @example * * async.map(['file1','file2','file3'], fs.stat, function(err, results) { * // results is now an array of stats for each file * }); */ -export default doParallel(map); +function map (coll, iteratee, callback) { + return _map(eachOf, coll, iteratee, callback) +} +export default awaitify(map, 3); diff --git a/lib/mapLimit.js b/lib/mapLimit.js index 905edc64c..63fade22c 100644 --- a/lib/mapLimit.js +++ b/lib/mapLimit.js @@ -1,5 +1,6 @@ -import doParallelLimit from './internal/doParallelLimit'; -import map from './internal/map'; +import _map from './internal/map'; +import eachOfLimit from './internal/eachOfLimit' +import awaitify from './internal/awaitify' /** * The same as [`map`]{@link module:Collections.map} but runs a maximum of `limit` async operations at a time. @@ -19,5 +20,9 @@ import map from './internal/map'; * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. Results is an array of the * transformed items from the `coll`. Invoked with (err, results). + * @returns {Promise} a promise, if no callback is passed */ -export default doParallelLimit(map); +function mapLimit (coll, limit, iteratee, callback) { + return _map(eachOfLimit(limit), coll, iteratee, callback) +} +export default awaitify(mapLimit, 4); diff --git a/lib/mapSeries.js b/lib/mapSeries.js index 96b5f0ad7..55c683124 100644 --- a/lib/mapSeries.js +++ b/lib/mapSeries.js @@ -1,5 +1,6 @@ -import mapLimit from './mapLimit'; -import doLimit from './internal/doLimit'; +import _map from './internal/map'; +import eachOfSeries from './eachOfSeries' +import awaitify from './internal/awaitify' /** * The same as [`map`]{@link module:Collections.map} but runs only a single async operation at a time. @@ -18,5 +19,9 @@ import doLimit from './internal/doLimit'; * @param {Function} [callback] - A callback which is called when all `iteratee` * functions have finished, or an error occurs. Results is an array of the * transformed items from the `coll`. Invoked with (err, results). + * @returns {Promise} a promise, if no callback is passed */ -export default doLimit(mapLimit, 1); +function mapSeries (coll, iteratee, callback) { + return _map(eachOfSeries, coll, iteratee, callback) +} +export default awaitify(mapSeries, 3); diff --git a/lib/mapValues.js b/lib/mapValues.js index d6a4ad23f..79edfd46e 100644 --- a/lib/mapValues.js +++ b/lib/mapValues.js @@ -1,6 +1,4 @@ import mapValuesLimit from './mapValuesLimit'; -import doLimit from './internal/doLimit'; - /** * A relative of [`map`]{@link module:Collections.map}, designed for use with objects. @@ -29,6 +27,7 @@ import doLimit from './internal/doLimit'; * 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. * Invoked with (err, result). + * @returns {Promise} a promise, if no callback is passed * @example * * async.mapValues({ @@ -46,5 +45,6 @@ import doLimit from './internal/doLimit'; * // } * }); */ - -export default doLimit(mapValuesLimit, Infinity); +export default function mapValues(obj, iteratee, callback) { + return mapValuesLimit(obj, Infinity, iteratee, callback) +} diff --git a/lib/mapValuesLimit.js b/lib/mapValuesLimit.js index 2267aafd7..af56f252e 100644 --- a/lib/mapValuesLimit.js +++ b/lib/mapValuesLimit.js @@ -1,6 +1,5 @@ -import eachOfLimit from './eachOfLimit'; - -import noop from './internal/noop'; +import eachOfLimit from './internal/eachOfLimit' +import awaitify from './internal/awaitify' import once from './internal/once'; import wrapAsync from './internal/wrapAsync'; @@ -24,12 +23,13 @@ import wrapAsync from './internal/wrapAsync'; * 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. * Invoked with (err, result). + * @returns {Promise} a promise, if no callback is passed */ -export default function mapValuesLimit(obj, limit, iteratee, callback) { - callback = once(callback || noop); +function mapValuesLimit(obj, limit, iteratee, callback) { + callback = once(callback); var newObj = {}; var _iteratee = wrapAsync(iteratee) - eachOfLimit(obj, limit, (val, key, next) => { + return eachOfLimit(limit)(obj, (val, key, next) => { _iteratee(val, key, (err, result) => { if (err) return next(err); newObj[key] = result; @@ -37,3 +37,5 @@ export default function mapValuesLimit(obj, limit, iteratee, callback) { }); }, err => callback(err, newObj)); } + +export default awaitify(mapValuesLimit, 4) diff --git a/lib/mapValuesSeries.js b/lib/mapValuesSeries.js index ab0b8f845..59e4b1a54 100644 --- a/lib/mapValuesSeries.js +++ b/lib/mapValuesSeries.js @@ -1,5 +1,4 @@ import mapValuesLimit from './mapValuesLimit'; -import doLimit from './internal/doLimit'; /** * The same as [`mapValues`]{@link module:Collections.mapValues} but runs only a single async operation at a time. @@ -19,5 +18,8 @@ import doLimit from './internal/doLimit'; * 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. * Invoked with (err, result). + * @returns {Promise} a promise, if no callback is passed */ -export default doLimit(mapValuesLimit, 1); +export default function mapValuesSeries(obj, iteratee, callback) { + return mapValuesLimit(obj, 1, iteratee, callback) +} diff --git a/lib/parallel.js b/lib/parallel.js index edf8be48b..fafae9df7 100644 --- a/lib/parallel.js +++ b/lib/parallel.js @@ -1,5 +1,5 @@ import eachOf from './eachOf'; -import parallel from './internal/parallel'; +import _parallel from './internal/parallel'; /** * Run the `tasks` collection of functions in parallel, without waiting until @@ -34,6 +34,7 @@ import parallel from './internal/parallel'; * 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). + * @returns {Promise} a promise, if a callback is not passed * * @example * async.parallel([ @@ -70,6 +71,6 @@ import parallel from './internal/parallel'; * // results is now equals to: {one: 1, two: 2} * }); */ -export default function parallelLimit(tasks, callback) { - parallel(eachOf, tasks, callback); +export default function parallel(tasks, callback) { + return _parallel(eachOf, tasks, callback); } diff --git a/lib/parallelLimit.js b/lib/parallelLimit.js index 9a857b844..ee4335c89 100644 --- a/lib/parallelLimit.js +++ b/lib/parallelLimit.js @@ -19,7 +19,8 @@ import parallel from './internal/parallel'; * 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). + * @returns {Promise} a promise, if a callback is not passed */ export default function parallelLimit(tasks, limit, callback) { - parallel(eachOfLimit(limit), tasks, callback); + return parallel(eachOfLimit(limit), tasks, callback); } diff --git a/lib/priorityQueue.js b/lib/priorityQueue.js index aa9b578c9..4eaea38bf 100644 --- a/lib/priorityQueue.js +++ b/lib/priorityQueue.js @@ -1,7 +1,4 @@ -import noop from './internal/noop'; - import setImmediate from './setImmediate'; - import queue from './queue'; /** @@ -32,8 +29,7 @@ export default function(worker, concurrency) { var q = queue(worker, concurrency); // Override push to accept second parameter representing priority - q.push = function(data, priority, callback) { - if (callback == null) callback = noop; + q.push = function(data, priority = 0, callback = () => {}) { if (typeof callback !== 'function') { throw new Error('task callback must be a function'); } @@ -46,7 +42,6 @@ export default function(worker, concurrency) { return setImmediate(() => q.drain()); } - priority = priority || 0; var nextNode = q._tasks.head; while (nextNode && priority >= nextNode.priority) { nextNode = nextNode.next; diff --git a/lib/race.js b/lib/race.js index 0593a7238..05101a3bd 100644 --- a/lib/race.js +++ b/lib/race.js @@ -1,6 +1,6 @@ -import noop from './internal/noop'; import once from './internal/once'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify'; /** * Runs the `tasks` array of functions in parallel, without waiting until the @@ -38,11 +38,13 @@ import wrapAsync from './internal/wrapAsync'; * // the result will be equal to 'two' as it finishes earlier * }); */ -export default function race(tasks, callback) { - callback = once(callback || noop); +function race(tasks, callback) { + callback = once(callback); if (!Array.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++) { wrapAsync(tasks[i])(callback); } } + +export default awaitify(race, 2) diff --git a/lib/reduce.js b/lib/reduce.js index 1a6c036b7..571bc5a6f 100644 --- a/lib/reduce.js +++ b/lib/reduce.js @@ -1,7 +1,7 @@ import eachOfSeries from './eachOfSeries'; -import noop from './internal/noop'; import once from './internal/once'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify' /** * Reduces `coll` into a single value using an async `iteratee` to return each @@ -32,6 +32,7 @@ import wrapAsync from './internal/wrapAsync'; * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Result is the reduced value. Invoked with * (err, result). + * @returns {Promise} a promise, if no callback is passed * @example * * async.reduce([1,2,3], 0, function(memo, item, callback) { @@ -43,13 +44,15 @@ import wrapAsync from './internal/wrapAsync'; * // result is now equal to the last value of memo, which is 6 * }); */ -export default function reduce(coll, memo, iteratee, callback) { - callback = once(callback || noop); +function reduce(coll, memo, iteratee, callback) { + callback = once(callback); var _iteratee = wrapAsync(iteratee); - eachOfSeries(coll, (x, i, iterCb) => { + return eachOfSeries(coll, (x, i, iterCb) => { _iteratee(memo, x, (err, v) => { memo = v; iterCb(err); }); }, err => callback(err, memo)); } +export default awaitify(reduce, 4) + diff --git a/lib/reduceRight.js b/lib/reduceRight.js index dd56a88e3..994eaf9a1 100644 --- a/lib/reduceRight.js +++ b/lib/reduceRight.js @@ -21,8 +21,9 @@ import reduce from './reduce'; * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Result is the reduced value. Invoked with * (err, result). + * @returns {Promise} a promise, if no callback is passed */ export default function reduceRight (array, memo, iteratee, callback) { var reversed = [...array].reverse(); - reduce(reversed, memo, iteratee, callback); + return reduce(reversed, memo, iteratee, callback); } diff --git a/lib/reject.js b/lib/reject.js index cfac12d84..c1de82589 100644 --- a/lib/reject.js +++ b/lib/reject.js @@ -1,5 +1,6 @@ -import reject from './internal/reject'; -import doParallel from './internal/doParallel'; +import _reject from './internal/reject'; +import eachOf from './eachOf' +import awaitify from './internal/awaitify' /** * The opposite of [`filter`]{@link module:Collections.filter}. Removes values that pass an `async` truth test. @@ -17,6 +18,7 @@ import doParallel from './internal/doParallel'; * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Invoked with (err, results). + * @returns {Promise} a promise, if no callback is passed * @example * * async.reject(['file1','file2','file3'], function(filePath, callback) { @@ -28,4 +30,7 @@ import doParallel from './internal/doParallel'; * createFiles(results); * }); */ -export default doParallel(reject); +function reject (coll, iteratee, callback) { + return _reject(eachOf, coll, iteratee, callback) +} +export default awaitify(reject, 3); diff --git a/lib/rejectLimit.js b/lib/rejectLimit.js index 76e6ffc27..d58b291f0 100644 --- a/lib/rejectLimit.js +++ b/lib/rejectLimit.js @@ -1,6 +1,6 @@ -import reject from './internal/reject'; -import doParallelLimit from './internal/doParallelLimit'; - +import _reject from './internal/reject'; +import eachOfLimit from './internal/eachOfLimit' +import awaitify from './internal/awaitify' /** * The same as [`reject`]{@link module:Collections.reject} but runs a maximum of `limit` async operations at a * time. @@ -19,5 +19,9 @@ import doParallelLimit from './internal/doParallelLimit'; * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Invoked with (err, results). + * @returns {Promise} a promise, if no callback is passed */ -export default doParallelLimit(reject); +function rejectLimit (coll, limit, iteratee, callback) { + return _reject(eachOfLimit(limit), coll, iteratee, callback) +} +export default awaitify(rejectLimit, 4); diff --git a/lib/rejectSeries.js b/lib/rejectSeries.js index 3f2d62f17..56b08a308 100644 --- a/lib/rejectSeries.js +++ b/lib/rejectSeries.js @@ -1,5 +1,6 @@ -import rejectLimit from './rejectLimit'; -import doLimit from './internal/doLimit'; +import _reject from './internal/reject'; +import eachOfSeries from './eachOfSeries' +import awaitify from './internal/awaitify' /** * The same as [`reject`]{@link module:Collections.reject} but runs only a single async operation at a time. @@ -17,5 +18,9 @@ import doLimit from './internal/doLimit'; * Invoked with (item, callback). * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Invoked with (err, results). + * @returns {Promise} a promise, if no callback is passed */ -export default doLimit(rejectLimit, 1); +function rejectSeries (coll, iteratee, callback) { + return _reject(eachOfSeries, coll, iteratee, callback) +} +export default awaitify(rejectSeries, 3); diff --git a/lib/retry.js b/lib/retry.js index 2a353dc32..5ef697119 100644 --- a/lib/retry.js +++ b/lib/retry.js @@ -1,5 +1,5 @@ -import noop from './internal/noop'; import wrapAsync from './internal/wrapAsync'; +import { promiseCallback, PROMISE_SYMBOL } from './internal/promiseCallback'; function constant(value) { return function () { @@ -39,6 +39,7 @@ function constant(value) { * 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). + * @returns {Promise} a promise if no callback provided * * @example * @@ -101,11 +102,11 @@ export default function retry(opts, task, callback) { }; if (arguments.length < 3 && typeof opts === 'function') { - callback = task || noop; + callback = task || promiseCallback(); task = opts; } else { parseTimes(options, opts); - callback = callback || noop; + callback = callback || promiseCallback(); } if (typeof task !== 'function') { @@ -129,6 +130,7 @@ export default function retry(opts, task, callback) { } retryAttempt(); + return callback[PROMISE_SYMBOL] } function parseTimes(acc, t) { diff --git a/lib/retryable.js b/lib/retryable.js index 9422eed1c..ad99801d1 100644 --- a/lib/retryable.js +++ b/lib/retryable.js @@ -1,6 +1,7 @@ import retry from './retry'; import initialParams from './internal/initialParams'; -import wrapAsync from './internal/wrapAsync'; +import {default as wrapAsync, isAsync} from './internal/wrapAsync'; +import { promiseCallback, PROMISE_SYMBOL } from './internal/promiseCallback'; /** * A close relative of [`retry`]{@link module:ControlFlow.retry}. This method @@ -14,7 +15,8 @@ import wrapAsync from './internal/wrapAsync'; * @see [async.retry]{@link module:ControlFlow.retry} * @category Control Flow * @param {Object|number} [opts = {times: 5, interval: 0}| 5] - optional - * options, exactly the same as from `retry` + * options, exactly the same as from `retry`, except for a `opts.arity` that + * is the arity of the `task` function, defaulting to `task.length` * @param {AsyncFunction} task - the asynchronous function to wrap. * This function will be passed any arguments passed to the returned wrapper. * Invoked with (...args, callback). @@ -30,13 +32,21 @@ import wrapAsync from './internal/wrapAsync'; * })] * }, callback); */ -export default function (opts, task) { +export default function retryable (opts, task) { if (!task) { task = opts; opts = null; } + let arity = (opts && opts.arity) || task.length + if (isAsync(task)) { + arity += 1 + } var _task = wrapAsync(task); return initialParams((args, callback) => { + if (args.length < arity - 1 || callback == null) { + args.push(callback) + callback = promiseCallback() + } function taskFn(cb) { _task(...args, cb); } @@ -44,5 +54,6 @@ export default function (opts, task) { if (opts) retry(opts, taskFn, callback); else retry(taskFn, callback); + return callback[PROMISE_SYMBOL] }); } diff --git a/lib/seq.js b/lib/seq.js index 1363cbdcd..6c9ede711 100644 --- a/lib/seq.js +++ b/lib/seq.js @@ -1,6 +1,6 @@ -import noop from './internal/noop'; import reduce from './reduce'; import wrapAsync from './internal/wrapAsync'; +import { promiseCallback, PROMISE_SYMBOL } from './internal/promiseCallback'; /** * Version of the compose function that is more natural to read. Each function @@ -49,7 +49,7 @@ export default function seq(...functions) { if (typeof cb == 'function') { args.pop(); } else { - cb = noop; + cb = promiseCallback(); } reduce(_functions, args, (newargs, fn, iterCb) => { @@ -58,5 +58,7 @@ export default function seq(...functions) { })); }, (err, results) => cb(err, ...results)); + + return cb[PROMISE_SYMBOL] }; } diff --git a/lib/series.js b/lib/series.js index 539a1e7cb..71367a954 100644 --- a/lib/series.js +++ b/lib/series.js @@ -1,4 +1,4 @@ -import parallel from './internal/parallel'; +import _parallel from './internal/parallel'; import eachOfSeries from './eachOfSeries'; /** @@ -34,6 +34,7 @@ import eachOfSeries from './eachOfSeries'; * functions have completed. This function gets a results array (or object) * containing all the result arguments passed to the `task` callbacks. Invoked * with (err, result). + * @return {Promise} a promise, if no callback is passed * @example * async.series([ * function(callback) { @@ -66,5 +67,5 @@ import eachOfSeries from './eachOfSeries'; * }); */ export default function series(tasks, callback) { - parallel(eachOfSeries, tasks, callback); + return _parallel(eachOfSeries, tasks, callback); } diff --git a/lib/some.js b/lib/some.js index 65543bb48..378ccdf5d 100644 --- a/lib/some.js +++ b/lib/some.js @@ -1,5 +1,6 @@ import createTester from './internal/createTester'; -import doParallel from './internal/doParallel'; +import eachOf from './eachOf' +import awaitify from './internal/awaitify' /** * Returns `true` if at least one element in the `coll` satisfies an async test. @@ -21,6 +22,7 @@ import doParallel from './internal/doParallel'; * 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 * tests. Invoked with (err, result). + * @returns {Promise} a promise, if no callback provided * @example * * async.some(['file1','file2','file3'], function(filePath, callback) { @@ -31,4 +33,7 @@ import doParallel from './internal/doParallel'; * // if result is true then at least one of the files exists * }); */ -export default doParallel(createTester(Boolean, res => res)); +function some(coll, iteratee, callback) { + return createTester(Boolean, res => res)(eachOf, coll, iteratee, callback) +} +export default awaitify(some, 3); diff --git a/lib/someLimit.js b/lib/someLimit.js index fb6841c50..2defb28d6 100644 --- a/lib/someLimit.js +++ b/lib/someLimit.js @@ -1,5 +1,6 @@ import createTester from './internal/createTester'; -import doParallelLimit from './internal/doParallelLimit'; +import eachOfLimit from './internal/eachOfLimit' +import awaitify from './internal/awaitify' /** * The same as [`some`]{@link module:Collections.some} but runs a maximum of `limit` async operations at a time. @@ -21,5 +22,9 @@ import doParallelLimit from './internal/doParallelLimit'; * 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 * tests. Invoked with (err, result). + * @returns {Promise} a promise, if no callback provided */ -export default doParallelLimit(createTester(Boolean, res => res)); +function someLimit(coll, limit, iteratee, callback) { + return createTester(Boolean, res => res)(eachOfLimit(limit), coll, iteratee, callback) +} +export default awaitify(someLimit, 4); diff --git a/lib/someSeries.js b/lib/someSeries.js index c05ca1f22..78421247e 100644 --- a/lib/someSeries.js +++ b/lib/someSeries.js @@ -1,5 +1,6 @@ -import someLimit from './someLimit'; -import doLimit from './internal/doLimit'; +import createTester from './internal/createTester'; +import eachOfSeries from './eachOfSeries'; +import awaitify from './internal/awaitify'; /** * The same as [`some`]{@link module:Collections.some} but runs only a single async operation at a time. @@ -20,5 +21,9 @@ import doLimit from './internal/doLimit'; * 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 * tests. Invoked with (err, result). + * @returns {Promise} a promise, if no callback provided */ -export default doLimit(someLimit, 1); +function someSeries(coll, iteratee, callback) { + return createTester(Boolean, res => res)(eachOfSeries, coll, iteratee, callback) +} +export default awaitify(someSeries, 3); diff --git a/lib/sortBy.js b/lib/sortBy.js index ba2a80407..ec724b6e8 100644 --- a/lib/sortBy.js +++ b/lib/sortBy.js @@ -1,5 +1,6 @@ import map from './map'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify' /** * Sorts a list by the results of running each `coll` value through an async @@ -20,6 +21,7 @@ import wrapAsync from './internal/wrapAsync'; * `iteratee` functions have finished, or an error occurs. Results is the items * from the original `coll` sorted by the values returned by the `iteratee` * calls. Invoked with (err, results). + * @returns {Promise} a promise, if no callback passed * @example * * async.sortBy(['file1','file2','file3'], function(file, callback) { @@ -48,9 +50,9 @@ import wrapAsync from './internal/wrapAsync'; * // result callback * }); */ -export default function sortBy (coll, iteratee, callback) { +function sortBy (coll, iteratee, callback) { var _iteratee = wrapAsync(iteratee); - map(coll, (x, iterCb) => { + return map(coll, (x, iterCb) => { _iteratee(x, (err, criteria) => { if (err) return iterCb(err); iterCb(null, {value: x, criteria}); @@ -65,3 +67,4 @@ export default function sortBy (coll, iteratee, callback) { return a < b ? -1 : a > b ? 1 : 0; } } +export default awaitify(sortBy, 3) diff --git a/lib/times.js b/lib/times.js index ebd336c4c..15e798fda 100644 --- a/lib/times.js +++ b/lib/times.js @@ -1,5 +1,4 @@ import timesLimit from './timesLimit'; -import doLimit from './internal/doLimit'; /** * Calls the `iteratee` function `n` times, and accumulates results in the same @@ -15,6 +14,7 @@ import doLimit from './internal/doLimit'; * @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}. + * @returns {Promise} a promise, if no callback is provided * @example * * // Pretend this is some complicated async factory @@ -33,4 +33,6 @@ import doLimit from './internal/doLimit'; * // we should now have 5 users * }); */ -export default doLimit(timesLimit, Infinity); +export default function times (n, iteratee, callback) { + return timesLimit(n, Infinity, iteratee, callback) +} diff --git a/lib/timesLimit.js b/lib/timesLimit.js index bb4a1fec7..8880ccfed 100644 --- a/lib/timesLimit.js +++ b/lib/timesLimit.js @@ -17,8 +17,9 @@ import wrapAsync from './internal/wrapAsync'; * @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}. + * @returns {Promise} a promise, if no callback is provided */ -export default function timeLimit(count, limit, iteratee, callback) { +export default function timesLimit(count, limit, iteratee, callback) { var _iteratee = wrapAsync(iteratee); - mapLimit(range(count), limit, _iteratee, callback); + return mapLimit(range(count), limit, _iteratee, callback); } diff --git a/lib/timesSeries.js b/lib/timesSeries.js index 17ec0ca85..b56f6ec86 100644 --- a/lib/timesSeries.js +++ b/lib/timesSeries.js @@ -1,5 +1,4 @@ import timesLimit from './timesLimit'; -import doLimit from './internal/doLimit'; /** * The same as [times]{@link module:ControlFlow.times} but runs only a single async operation at a time. @@ -14,5 +13,8 @@ import doLimit from './internal/doLimit'; * @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}. + * @returns {Promise} a promise, if no callback is provided */ -export default doLimit(timesLimit, 1); +export default function timesSeries (n, iteratee, callback) { + return timesLimit(n, 1, iteratee, callback) +} diff --git a/lib/transform.js b/lib/transform.js index 821a0fa47..786f5d391 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -1,7 +1,7 @@ import eachOf from './eachOf'; -import noop from './internal/noop'; import once from './internal/once'; import wrapAsync from './internal/wrapAsync'; +import { promiseCallback, PROMISE_SYMBOL } from './internal/promiseCallback'; /** * A relative of `reduce`. Takes an Object or Array, and iterates over each @@ -22,6 +22,7 @@ import wrapAsync from './internal/wrapAsync'; * @param {Function} [callback] - A callback which is called after all the * `iteratee` functions have finished. Result is the transformed accumulator. * Invoked with (err, result). + * @returns {Promise} a promise, if no callback provided * @example * * async.transform([1,2,3], function(acc, item, index, callback) { @@ -46,15 +47,16 @@ import wrapAsync from './internal/wrapAsync'; * }) */ export default function transform (coll, accumulator, iteratee, callback) { - if (arguments.length <= 3) { + if (arguments.length <= 3 && typeof accumulator === 'function') { callback = iteratee; iteratee = accumulator; accumulator = Array.isArray(coll) ? [] : {}; } - callback = once(callback || noop); + callback = once(callback || promiseCallback()); var _iteratee = wrapAsync(iteratee); eachOf(coll, (v, k, cb) => { _iteratee(accumulator, v, k, cb); }, err => callback(err, accumulator)); + return callback[PROMISE_SYMBOL] } diff --git a/lib/tryEach.js b/lib/tryEach.js index af885dc05..6677c9ddb 100644 --- a/lib/tryEach.js +++ b/lib/tryEach.js @@ -1,6 +1,6 @@ import eachSeries from './eachSeries'; -import noop from './internal/noop'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify' /** * It runs each task in series but stops whenever any of the functions were @@ -21,6 +21,7 @@ import wrapAsync from './internal/wrapAsync'; * of the tasks has succeeded, or all have failed. It receives the `err` and * `result` arguments of the last attempt at completing the `task`. Invoked with * (err, results). + * @returns {Promise} a promise, if no callback is passed * @example * async.tryEach([ * function getDataFromFirstWebsite(callback) { @@ -39,11 +40,10 @@ import wrapAsync from './internal/wrapAsync'; * }); * */ -export default function tryEach(tasks, callback) { +function tryEach(tasks, callback) { var error = null; var result; - callback = callback || noop; - eachSeries(tasks, (task, taskCb) => { + return eachSeries(tasks, (task, taskCb) => { wrapAsync(task)((err, ...args) => { if (args.length < 2) { [result] = args; @@ -55,3 +55,5 @@ export default function tryEach(tasks, callback) { }); }, () => callback(error, result)); } + +export default awaitify(tryEach) diff --git a/lib/until.js b/lib/until.js index 37d892269..0c407a308 100644 --- a/lib/until.js +++ b/lib/until.js @@ -22,6 +22,7 @@ import wrapAsync from './internal/wrapAsync'; * 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]); + * @returns {Promise} a promise, if a callback is not passed * * @example * const results = [] @@ -39,5 +40,5 @@ import wrapAsync from './internal/wrapAsync'; */ export default function until(test, iteratee, callback) { const _test = wrapAsync(test) - whilst((cb) => _test((err, truth) => cb (err, !truth)), iteratee, callback); + return whilst((cb) => _test((err, truth) => cb (err, !truth)), iteratee, callback); } diff --git a/lib/waterfall.js b/lib/waterfall.js index f4c3ddce7..f9a6c2417 100644 --- a/lib/waterfall.js +++ b/lib/waterfall.js @@ -1,8 +1,8 @@ -import noop from './internal/noop'; import once from './internal/once'; import onlyOnce from './internal/onlyOnce'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify' /** * Runs the `tasks` array of functions in series, each passing their results to @@ -61,8 +61,8 @@ import wrapAsync from './internal/wrapAsync'; * callback(null, 'done'); * } */ -export default function(tasks, callback) { - callback = once(callback || noop); +function waterfall (tasks, callback) { + callback = once(callback); if (!Array.isArray(tasks)) return callback(new Error('First argument to waterfall must be an array of functions')); if (!tasks.length) return callback(); var taskIndex = 0; @@ -82,3 +82,5 @@ export default function(tasks, callback) { nextTask([]); } + +export default awaitify(waterfall) diff --git a/lib/whilst.js b/lib/whilst.js index d42e6a798..1013d37ad 100644 --- a/lib/whilst.js +++ b/lib/whilst.js @@ -1,7 +1,6 @@ -import noop from './internal/noop'; - import onlyOnce from './internal/onlyOnce'; import wrapAsync from './internal/wrapAsync'; +import awaitify from './internal/awaitify'; /** * Repeatedly call `iteratee`, while `test` returns `true`. Calls `callback` when @@ -20,7 +19,7 @@ import wrapAsync from './internal/wrapAsync'; * 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 * callback. Invoked with (err, [results]); - * @returns undefined + * @returns {Promise} a promise, if no callback is passed * @example * * var count = 0; @@ -37,8 +36,8 @@ import wrapAsync from './internal/wrapAsync'; * } * ); */ -export default function whilst(test, iteratee, callback) { - callback = onlyOnce(callback || noop); +function whilst(test, iteratee, callback) { + callback = onlyOnce(callback); var _fn = wrapAsync(iteratee); var _test = wrapAsync(test); var results @@ -59,3 +58,4 @@ export default function whilst(test, iteratee, callback) { return _test(check); } +export default awaitify(whilst, 3) diff --git a/test/asyncFunctions.js b/test/asyncFunctions.js index 6bd7a8d11..e24bd0c63 100644 --- a/test/asyncFunctions.js +++ b/test/asyncFunctions.js @@ -27,6 +27,9 @@ describe('async function support', function () { if (supportsAsync()) { require('./es2017/asyncFunctions.js').call(this); + describe('awaitable functions', () => { + require('./es2017/awaitableFunctions.js').call(this); + }); } else { it('should not test async functions in this environment'); } diff --git a/test/es2017/awaitableFunctions.js b/test/es2017/awaitableFunctions.js new file mode 100644 index 000000000..b45b56cf8 --- /dev/null +++ b/test/es2017/awaitableFunctions.js @@ -0,0 +1,579 @@ +var async = require('../../lib'); +const {expect} = require('chai'); +const {default: wrapAsync} = require('../../lib/internal/wrapAsync') + + +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('asyncify should not add an additional level of wrapping', () => { + const wrapped = wrapAsync(async.each) + let sameStack = false + wrapped([1], (val, cb) => cb(), () => {sameStack = true}) + expect(sameStack).to.equal(true) + }) + + it('should throw as expected (async)', async () => { + try { + await async.each(input, async val => { throw new Error(val) }); + } catch (e) { + var thrown = e + } + expect(thrown).to.be.an('error') + }); + + it('should throw as expected (callback)', async () => { + let thrown + await async.each(input, (val) => { + throw new Error(val) + }).catch(e => {thrown = e}) + expect(thrown).to.be.an('error') + }) + + it('should throw as expected (callback, try/catch)', async () => { + try { + await async.each(input, (val, cb) => { cb(new Error(val)) }); + } catch (e) { + var thrown = e + } + expect(thrown).to.be.an('error') + }); + + /* + * Collections + */ + + it('should return a Promise: each', async () => { + expect (async.each.name).to.contain('each') + const calls = [] + await async.each(input, async val => { calls.push(val) }); + expect(calls).to.eql([1, 2, 3]) + expect(async.each(input, asyncIdentity) instanceof Promise).to.equal(true) + }); + it('should return a Promise: eachSeries', async () => { + expect (async.eachSeries.name).to.contain('eachSeries') + const calls = [] + await async.eachSeries(input, async val => { calls.push(val) }); + expect(calls).to.eql([1, 2, 3]) + }); + it('should return a Promise: eachLimit', async () => { + expect (async.eachLimit.name).to.contain('eachLimit') + const calls = [] + await async.eachLimit(input, 1, async val => { calls.push(val) }); + expect(calls).to.eql([1, 2, 3]) + }); + + it('should return a Promise: eachOf', async () => { + expect (async.eachOf.name).to.contain('eachOf') + const calls = [] + await async.eachOf(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1, 'a'], [2, 'b'], [3, 'c']]) + }); + it('should return a Promise: eachOfSeries', async () => { + expect (async.eachOfSeries.name).to.contain('eachOfSeries') + const calls = [] + await async.eachOfSeries(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1, 'a'], [2, 'b'], [3, 'c']]) + }); + it('should return a Promise: eachOfLimit', async () => { + expect (async.eachOfLimit.name).to.contain('eachOfLimit') + const calls = [] + await async.eachOfLimit(inputObj, 1, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1, 'a'], [2, 'b'], [3, 'c']]) + }); + + it('should return a Promise: concat', async () => { + expect (async.concat.name).to.contain('concat') + const calls = [] + await async.concat(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: concatSeries', async () => { + expect (async.concatSeries.name).to.contain('concatSeries') + const calls = [] + await async.concatSeries(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: concatLimit', async () => { + expect (async.concatLimit.name).to.contain('concatLimit') + const calls = [] + await async.concatLimit(inputObj, 1, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + + it('should return a Promise: detect', async () => { + expect (async.detect.name).to.contain('detect') + const calls = [] + await async.detect(input, async (...args) => { calls.push(args); return args[0] === 3 }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: detectSeries', async () => { + expect (async.detectSeries.name).to.contain('detectSeries') + const calls = [] + await async.detectSeries(input, async (...args) => { calls.push(args); return args[0] === 3 }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: detectLimit', async () => { + expect (async.detectLimit.name).to.contain('detectLimit') + const calls = [] + await async.detectLimit(input, 1, async (...args) => { calls.push(args); return args[0] === 3 }); + expect(calls).to.eql([[1], [2], [3]]) + }); + + it('should return a Promise: every', async () => { + expect (async.every.name).to.contain('every') + const calls = [] + await async.every(input, async (...args) => { calls.push(args); return args[0] !== 3 }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: everySeries', async () => { + expect (async.everySeries.name).to.contain('everySeries') + const calls = [] + await async.everySeries(input, async (...args) => { calls.push(args); return args[0] !== 3 }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: everyLimit', async () => { + expect (async.everyLimit.name).to.contain('everyLimit') + const calls = [] + await async.everyLimit(input, 1, async (...args) => { calls.push(args); return args[0] !== 3 }); + expect(calls).to.eql([[1], [2], [3]]) + }); + + it('should return a Promise: filter', async () => { + expect (async.filter.name).to.contain('filter') + const calls = [] + await async.filter(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: filterSeries', async () => { + expect (async.filterSeries.name).to.contain('filterSeries') + const calls = [] + await async.filterSeries(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: filterLimit', async () => { + expect (async.filterLimit.name).to.contain('filterLimit') + const calls = [] + await async.filterLimit(inputObj, 1, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + + it('should return a Promise: groupBy', async () => { + expect (async.groupBy.name).to.contain('groupBy') + const calls = [] + await async.groupBy(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: groupBySeries', async () => { + expect (async.groupBySeries.name).to.contain('groupBySeries') + const calls = [] + await async.groupBySeries(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: groupByLimit', async () => { + expect (async.groupByLimit.name).to.contain('groupByLimit') + const calls = [] + await async.groupByLimit(inputObj, 1, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + + it('should return a Promise: map', async () => { + expect (async.map.name).to.contain('map') + const calls = [] + await async.map(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: mapSeries', async () => { + expect (async.mapSeries.name).to.contain('mapSeries') + const calls = [] + await async.mapSeries(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: mapLimit', async () => { + expect (async.mapLimit.name).to.contain('mapLimit') + const calls = [] + await async.mapLimit(inputObj, 1, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + + it('should return a Promise: mapValues', async () => { + expect (async.mapValues.name).to.contain('mapValues') + const calls = [] + await async.mapValues(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1, 'a'], [2, 'b'], [3, 'c']]) + }); + it('should return a Promise: mapValuesSeries', async () => { + expect (async.mapValuesSeries.name).to.contain('mapValuesSeries') + const calls = [] + await async.mapValuesSeries(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1, 'a'], [2, 'b'], [3, 'c']]) + }); + it('should return a Promise: mapValuesLimit', async () => { + expect (async.mapValuesLimit.name).to.contain('mapValuesLimit') + const calls = [] + await async.mapValuesLimit(inputObj, 1, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1, 'a'], [2, 'b'], [3, 'c']]) + }); + + + it('should return a Promise: reduce', async () => { + expect (async.reduce.name).to.contain('reduce') + const calls = [] + await async.reduce(input, 1, async (...args) => calls.push(args)); + expect(calls).to.eql([[1, 1], [1, 2], [2, 3]]) + }); + it('should return a Promise: reduceRight', async () => { + expect (async.reduceRight.name).to.contain('reduceRight') + const calls = [] + await async.reduceRight(input, 1, async (...args) => calls.push(args)); + expect(calls).to.eql([[1, 3], [1, 2], [2, 1]]) + }); + + it('should return a Promise: reject', async () => { + expect (async.reject.name).to.contain('reject') + const calls = [] + await async.reject(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: rejectSeries', async () => { + expect (async.rejectSeries.name).to.contain('rejectSeries') + const calls = [] + await async.rejectSeries(inputObj, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: rejectLimit', async () => { + expect (async.rejectLimit.name).to.contain('rejectLimit') + const calls = [] + await async.rejectLimit(inputObj, 1, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + + it('should return a Promise: some', async () => { + expect (async.some.name).to.contain('some') + const calls = [] + await async.some(input, async (...args) => { calls.push(args); return args[0] === 3 }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: someSeries', async () => { + expect (async.someSeries.name).to.contain('someSeries') + const calls = [] + await async.someSeries(input, async (...args) => { calls.push(args); return args[0] === 3 }); + expect(calls).to.eql([[1], [2], [3]]) + }); + it('should return a Promise: someLimit', async () => { + expect (async.someLimit.name).to.contain('someLimit') + const calls = [] + await async.someLimit(input, 1, async (...args) => { calls.push(args); return args[0] === 3 }); + expect(calls).to.eql([[1], [2], [3]]) + }); + + it('should return a Promise: sortBy', async () => { + expect (async.sortBy.name).to.contain('sortBy') + const calls = [] + await async.sortBy(input, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1], [2], [3]]) + }); + + it('should return a Promise: times', async () => { + expect (async.times.name).to.contain('times') + const calls = [] + await async.times(3, async (...args) => { calls.push(args); return args[0] === 3 }); + expect(calls).to.eql([[0], [1], [2]]) + }); + it('should return a Promise: timesSeries', async () => { + expect (async.timesSeries.name).to.contain('timesSeries') + const calls = [] + await async.timesSeries(3, async (...args) => { calls.push(args); return args[0] === 3 }); + expect(calls).to.eql([[0], [1], [2]]) + }); + it('should return a Promise: timesLimit', async () => { + expect (async.timesLimit.name).to.contain('timesLimit') + const calls = [] + await async.timesLimit(3, 1, async (...args) => { calls.push(args); return args[0] === 3 }); + expect(calls).to.eql([[0], [1], [2]]) + }); + + it('should return a Promise: transform', async () => { + expect (async.transform.name).to.contain('transform') + const calls = [] + await async.transform(inputObj, 1, async (...args) => calls.push(args)); + expect(calls).to.eql([[1, 1, 'a'], [1, 2, 'b'], [1, 3, 'c']]) + }); + it('should return a Promise: transform (2 args)', async () => { + expect (async.transform.name).to.contain('transform') + const calls = [] + await async.transform(inputObj, async (...args) => calls.push(args)); + expect(calls).to.eql([[{}, 1, 'a'], [{}, 2, 'b'], [{}, 3, 'c']]) + }); + + /* + * Control flow + */ + + // TODO: figure out to do with applyEach + + it('should return a Promise: auto', async () => { + expect (async.auto.name).to.contain('auto') + const calls = [] + await async.auto({ + async a () { + calls.push('a') + return Promise.resolve('a') + }, + b: ['a', 'c', async () => calls.push('b')], + async c () { + await Promise.resolve() + calls.push('c') + return Promise.resolve('c') + } + }); + expect(calls).to.eql(['a', 'c', 'b']) + }); + it('should return a Promise: autoInject', async () => { + expect (async.autoInject.name).to.contain('autoInject') + const calls = [] + await async.autoInject({ + async a () { + calls.push('a') + return 'a' + }, + async b(a, c) { calls.push('b'); calls.push(a, c) }, + async c () { + calls.push('c') + return 'c' + } + }, 1); + expect(calls).to.eql(['a', 'c', 'b', 'a', 'c']) + }); + + it('should return a Promise: compose', async () => { + expect (async.compose.name).to.contain('compose') + const calls = [] + const fn = async.compose( + async (...args) => calls.push('a', args), + async (...args) => calls.push('b', args) + ); + const result = await fn(1, 2) + expect(calls).to.eql(['b', [1, 2], 'a', [2]]) + expect(result).to.eql(4) + }); + it('should return a Promise: seq', async () => { + expect (async.seq.name).to.contain('seq') + const calls = [] + const fn = async.seq( + async (...args) => calls.push('a', args), + async (...args) => calls.push('b', args) + ); + const result = await fn(1, 2) + expect(calls).to.eql(['a', [1, 2], 'b', [2]]) + expect(result).to.eql(4) + }); + + it('should return a Promise: whilst', async () => { + expect (async.whilst.name).to.contain('whilst') + const calls = [] + let counter = 0 + await async.whilst( + async () => {calls.push('test', counter); return counter < 5}, + async () => { calls.push('fn'); counter++ } + ); + expect(calls).to.eql([ + 'test', 0, 'fn', + 'test', 1, 'fn', + 'test', 2, 'fn', + 'test', 3, 'fn', + 'test', 4, 'fn', + 'test', 5 + ]) + }); + it('should return a Promise: until', async () => { + expect (async.until.name).to.contain('until') + const calls = [] + let counter = 0 + await async.until( + async () => {calls.push('test', counter); return counter === 5}, + async () => { calls.push('fn'); counter++ } + ); + expect(calls).to.eql([ + 'test', 0, 'fn', + 'test', 1, 'fn', + 'test', 2, 'fn', + 'test', 3, 'fn', + 'test', 4, 'fn', + 'test', 5 + ]) + }); + it('should return a Promise: doWhilst', async () => { + expect (async.doWhilst.name).to.contain('doWhilst') + const calls = [] + let counter = 0 + await async.doWhilst( + async () => { calls.push('fn'); counter++ }, + async () => {calls.push('test', counter); return counter < 5} + ); + expect(calls).to.eql([ + 'fn', + 'test', 1, 'fn', + 'test', 2, 'fn', + 'test', 3, 'fn', + 'test', 4, 'fn', + 'test', 5 + ]) + }); + it('should return a Promise: doUntil', async () => { + expect (async.doUntil.name).to.contain('doUntil') + const calls = [] + let counter = 0 + await async.doUntil( + async () => { calls.push('fn'); counter++ }, + async () => {calls.push('test', counter); return counter === 5} + ); + expect(calls).to.eql([ + 'fn', + 'test', 1, 'fn', + 'test', 2, 'fn', + 'test', 3, 'fn', + 'test', 4, 'fn', + 'test', 5 + ]) + }); + + it('should return a Promise: forever', async () => { + expect (async.forever.name).to.contain('forever') + const calls = [] + let counter = 0 + try { + await async.forever(async () => { + calls.push(counter) + counter++ + await Promise.resolve() + if (counter === 5) throw new Error() + }) + } catch (e) { + var err = e + } + expect(calls).to.eql([0, 1, 2, 3, 4]) + expect(err).to.be.an('error') + }); + + it('should return a Promise: parallel', async () => { + expect (async.parallel.name).to.contain('parallel') + const calls = [] + await async.parallel([ + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + ]) + expect(calls).to.eql([1, 1, 1, 1]) + }); + it('should return a Promise: parallelLimit', async () => { + expect (async.parallelLimit.name).to.contain('parallelLimit') + const calls = [] + await async.parallelLimit([ + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + ], 2) + expect(calls).to.eql([1, 1, 1, 1]) + }); + it('should return a Promise: series', async () => { + expect (async.series.name).to.contain('series') + const calls = [] + await async.series([ + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + ], 2) + expect(calls).to.eql([1, 1, 1, 1]) + }); + + it('should return a Promise: race', async () => { + expect (async.race.name).to.contain('race') + const calls = [] + await async.race([ + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(1) }, + ], 2) + expect(calls).to.eql([1, 1, 1, 1]) + }); + + it('should return a Promise: retryable', async () => { + expect (async.retryable.name).to.contain('retryable') + let counter = 0 + const calls = [] + const fn = async.retryable(async (a, b) => { + calls.push(a, b) + counter++ + if (counter < 3) throw new Error() + }) + const promise = fn(1, 2) + expect(promise.then).to.be.a('function') + await promise + expect(calls).to.eql([1, 2, 1, 2, 1, 2]) + }); + it('should return a Promise: retryable (arity 0)', async () => { + expect (async.retryable.name).to.contain('retryable') + let counter = 0 + const calls = [] + const fn = async.retryable({times: 5}, async () => { + calls.push(0) + counter++ + if (counter < 3) throw new Error() + }) + await fn() + expect(calls).to.eql([0, 0, 0]) + }); + + it('should return a Promise: retry', async () => { + expect (async.retry.name).to.contain('retry') + let counter = 0 + const calls = [] + await async.retry(async () => { + calls.push(counter) + counter++ + if (counter < 3) throw new Error() + }) + expect(calls).to.eql([0, 1, 2]) + }); + + it('should return a Promise: tryEach', async () => { + expect (async.tryEach.name).to.contain('tryEach') + const calls = [] + await async.tryEach([ + async () => { await Promise.resolve(); calls.push(1); throw new Error() }, + async () => { await Promise.resolve(); calls.push(2); throw new Error() }, + async () => { await Promise.resolve(); calls.push(3) }, + async () => { await Promise.resolve(); calls.push(4) }, + ], 2) + expect(calls).to.eql([1, 2, 3]) + }); + + it('should return a Promise: waterfall', async () => { + expect (async.waterfall.name).to.contain('waterfall') + const calls = [] + await async.waterfall([ + async () => { await Promise.resolve(); calls.push(1) }, + async () => { await Promise.resolve(); calls.push(2) }, + async () => { await Promise.resolve(); calls.push(3) }, + async () => { await Promise.resolve(); calls.push(4) }, + ], 2) + expect(calls).to.eql([1, 2, 3, 4]) + }); + + /* + * Util + */ + + +}; diff --git a/test/retry.js b/test/retry.js index 7369f8a6c..e7bc36a40 100644 --- a/test/retry.js +++ b/test/retry.js @@ -115,7 +115,7 @@ describe("retry", () => { async.retry((cb) => { calls++; cb("fail"); - }); + }).catch(() => {}); setTimeout(() => { expect(calls).to.equal(5); done();