summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Early <alexander.early@gmail.com>2018-08-05 16:28:30 -0700
committerAlexander Early <alexander.early@gmail.com>2018-08-05 16:28:30 -0700
commit0d0c6d1a8b9ab6efe9c14d58c52ca225a67820bd (patch)
tree8e8b62a7d6436d0d1996072458341e65e4da3878
parent50939d7fa88523e9ee86f6bcc84c7bec0cc97bc4 (diff)
downloadasync-0d0c6d1a8b9ab6efe9c14d58c52ca225a67820bd.tar.gz
make each and family awaitable
-rw-r--r--lib/each.js8
-rw-r--r--lib/eachLimit.js7
-rw-r--r--lib/eachOf.js6
-rw-r--r--lib/eachOfLimit.js8
-rw-r--r--lib/eachOfSeries.js4
-rw-r--r--lib/eachSeries.js4
-rw-r--r--lib/internal/awaitify.js25
-rw-r--r--lib/internal/promiseCallback.js19
-rw-r--r--test/asyncFunctions.js3
-rw-r--r--test/es2017/awaitableFunctions.js59
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']])
+ });
+};