diff options
author | Alexander Early <alexander.early@gmail.com> | 2018-08-05 16:28:30 -0700 |
---|---|---|
committer | Alexander Early <alexander.early@gmail.com> | 2018-08-05 16:28:30 -0700 |
commit | 0d0c6d1a8b9ab6efe9c14d58c52ca225a67820bd (patch) | |
tree | 8e8b62a7d6436d0d1996072458341e65e4da3878 | |
parent | 50939d7fa88523e9ee86f6bcc84c7bec0cc97bc4 (diff) | |
download | async-0d0c6d1a8b9ab6efe9c14d58c52ca225a67820bd.tar.gz |
make each and family awaitable
-rw-r--r-- | lib/each.js | 8 | ||||
-rw-r--r-- | lib/eachLimit.js | 7 | ||||
-rw-r--r-- | lib/eachOf.js | 6 | ||||
-rw-r--r-- | lib/eachOfLimit.js | 8 | ||||
-rw-r--r-- | lib/eachOfSeries.js | 4 | ||||
-rw-r--r-- | lib/eachSeries.js | 4 | ||||
-rw-r--r-- | lib/internal/awaitify.js | 25 | ||||
-rw-r--r-- | lib/internal/promiseCallback.js | 19 | ||||
-rw-r--r-- | test/asyncFunctions.js | 3 | ||||
-rw-r--r-- | test/es2017/awaitableFunctions.js | 59 |
10 files changed, 133 insertions, 10 deletions
diff --git a/lib/each.js b/lib/each.js index dc856ac..7f6e689 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 e4d67cb..5c491e8 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 070204b..f3bda13 100644 --- a/lib/eachOf.js +++ b/lib/eachOf.js @@ -6,6 +6,7 @@ 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) { @@ -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,7 @@ var eachOfGeneric = doLimit(eachOfLimit, Infinity); * doSomethingWith(configs); * }); */ -export default function(coll, iteratee, callback) { +export default awaitify((coll, iteratee, callback) => { var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric; eachOfImplementation(coll, wrapAsync(iteratee), callback); -} +}, 3) diff --git a/lib/eachOfLimit.js b/lib/eachOfLimit.js index f439be6..f65d7d1 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 d64e7ec..fbb4e11 100644 --- a/lib/eachOfSeries.js +++ b/lib/eachOfSeries.js @@ -1,5 +1,6 @@ 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 +18,6 @@ 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); +export default awaitify(doLimit(eachOfLimit, 1), 3); diff --git a/lib/eachSeries.js b/lib/eachSeries.js index ae6f6c7..5e499b1 100644 --- a/lib/eachSeries.js +++ b/lib/eachSeries.js @@ -1,5 +1,6 @@ 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 +20,6 @@ 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); +export default awaitify(doLimit(eachLimit, 1), 3); diff --git a/lib/internal/awaitify.js b/lib/internal/awaitify.js new file mode 100644 index 0000000..eeda0e1 --- /dev/null +++ b/lib/internal/awaitify.js @@ -0,0 +1,25 @@ +export const ASYNC_FN = Symbol('asyncFunction') + +// conditionally promisify a function. +// only return a promise if a callback is omitted +export default function awaitify (asyncFn, arity) { + function awaitable(...args) { + if (args.length === arity || typeof args[arity - 1] === 'function') { + return asyncFn.apply(this, args) + } + + return new Promise((resolve, reject) => { + args.push((err, ...cbArgs) => { + if (err) return reject(err) + resolve(cbArgs.length > 1 ? cbArgs : cbArgs[0]) + }) + asyncFn.apply(this, args) + }) + } + + awaitable[Symbol.toStringTag] = 'AsyncFunction' + awaitable[ASYNC_FN] = true + awaitable.displayName = `awaitable(${asyncFn.name})` + + return awaitable +} diff --git a/lib/internal/promiseCallback.js b/lib/internal/promiseCallback.js new file mode 100644 index 0000000..685dd7a --- /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/test/asyncFunctions.js b/test/asyncFunctions.js index 6bd7a8d..e24bd0c 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 0000000..ce36867 --- /dev/null +++ b/test/es2017/awaitableFunctions.js @@ -0,0 +1,59 @@ +var async = require('../../lib'); +const {expect} = require('chai'); +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); + }) + }); + + + /* + * Collections + */ + + it('should return a Promise: each', async () => { + 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 () => { + 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 () => { + 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 () => { + 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 () => { + 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 () => { + const calls = [] + await async.eachOfLimit(inputObj, 1, async (...args) => { calls.push(args) }); + expect(calls).to.eql([[1, 'a'], [2, 'b'], [3, 'c']]) + }); +}; |