From 2030f5f1bd01a141ac4c85d3deab927f49e255fc Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Sun, 2 Apr 2017 22:53:16 -0700 Subject: [PATCH] initial experiment with returning promises --- lib/each.js | 3 ++- lib/eachOf.js | 8 +++++--- lib/eachOfLimit.js | 3 ++- lib/internal/eachOfLimit.js | 5 +++-- lib/internal/map.js | 7 +++++-- lib/internal/once.js | 4 +++- lib/internal/promiseCallback.js | 24 ++++++++++++++++++++++++ mocha_test/es2017/asyncFunctions.js | 21 ++++++++++++++------- 8 files changed, 58 insertions(+), 17 deletions(-) create mode 100644 lib/internal/promiseCallback.js diff --git a/lib/each.js b/lib/each.js index 29f2bafc8..28c9cb4d9 100644 --- a/lib/each.js +++ b/lib/each.js @@ -25,6 +25,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 the callback is omitted * @example * * // assuming openFiles is an array of file names and saveFile is a function @@ -60,5 +61,5 @@ import wrapAsync from './internal/wrapAsync' * }); */ export default function eachLimit(coll, iteratee, callback) { - eachOf(coll, withoutIndex(wrapAsync(iteratee)), callback); + return eachOf(coll, withoutIndex(wrapAsync(iteratee)), callback); } diff --git a/lib/eachOf.js b/lib/eachOf.js index 9ab3eb6d5..c27738e5c 100644 --- a/lib/eachOf.js +++ b/lib/eachOf.js @@ -3,14 +3,14 @@ import isArrayLike from 'lodash/isArrayLike'; import breakLoop from './internal/breakLoop'; import eachOfLimit from './eachOfLimit'; import doLimit from './internal/doLimit'; -import noop from 'lodash/noop'; import once from './internal/once'; import onlyOnce from './internal/onlyOnce'; import wrapAsync from './internal/wrapAsync'; +import promiseCallback from './internal/promiseCallback'; // eachOf implementation optimized for array-likes function eachOfArrayLike(coll, iteratee, callback) { - callback = once(callback || noop); + callback = once(callback || promiseCallback()); var index = 0, completed = 0, length = coll.length; @@ -29,6 +29,7 @@ function eachOfArrayLike(coll, iteratee, callback) { for (; index < length; index++) { iteratee(coll[index], index, onlyOnce(iteratorCallback)); } + return callback.promise; } // a generic version of eachOf which can handle array, object, and iterator cases. @@ -52,6 +53,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 the callback is omitted * @example * * var obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"}; @@ -75,5 +77,5 @@ var eachOfGeneric = doLimit(eachOfLimit, Infinity); */ export default function(coll, iteratee, callback) { var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric; - eachOfImplementation(coll, wrapAsync(iteratee), callback); + return eachOfImplementation(coll, wrapAsync(iteratee), callback); } diff --git a/lib/eachOfLimit.js b/lib/eachOfLimit.js index 1de28efc6..f5371e7ac 100644 --- a/lib/eachOfLimit.js +++ b/lib/eachOfLimit.js @@ -20,7 +20,8 @@ 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 the callback is omitted */ export default function eachOfLimit(coll, limit, iteratee, callback) { - _eachOfLimit(limit)(coll, wrapAsync(iteratee), callback); + return _eachOfLimit(limit)(coll, wrapAsync(iteratee), callback); } diff --git a/lib/internal/eachOfLimit.js b/lib/internal/eachOfLimit.js index da46fa45c..487ec3f30 100644 --- a/lib/internal/eachOfLimit.js +++ b/lib/internal/eachOfLimit.js @@ -1,14 +1,14 @@ -import noop from 'lodash/noop'; import once from './once'; import iterator from './iterator'; import onlyOnce from './onlyOnce'; import breakLoop from './breakLoop'; +import promiseCallback from './promiseCallback'; export default function _eachOfLimit(limit) { return function (obj, iteratee, callback) { - callback = once(callback || noop); + callback = once(callback || promiseCallback()); if (limit <= 0 || !obj) { return callback(null); } @@ -47,5 +47,6 @@ export default function _eachOfLimit(limit) { } replenish(); + return callback.promise; }; } diff --git a/lib/internal/map.js b/lib/internal/map.js index 9cd927e2c..d00c3a2c0 100644 --- a/lib/internal/map.js +++ b/lib/internal/map.js @@ -1,8 +1,9 @@ -import noop from 'lodash/noop'; +import once from './once'; import wrapAsync from './wrapAsync'; +import promiseCallback from './promiseCallback'; export default function _asyncMap(eachfn, arr, iteratee, callback) { - callback = callback || noop; + callback = once(callback || promiseCallback()); arr = arr || []; var results = []; var counter = 0; @@ -17,4 +18,6 @@ export default function _asyncMap(eachfn, arr, iteratee, callback) { }, function (err) { callback(err, results); }); + + return callback.promise; } diff --git a/lib/internal/once.js b/lib/internal/once.js index f601185b9..1094ad355 100644 --- a/lib/internal/once.js +++ b/lib/internal/once.js @@ -1,8 +1,10 @@ export default function once(fn) { - return function () { + function wrapped () { if (fn === null) return; var callFn = fn; fn = null; callFn.apply(this, arguments); }; + wrapped.promise = fn.promise; + return wrapped; } diff --git a/lib/internal/promiseCallback.js b/lib/internal/promiseCallback.js new file mode 100644 index 000000000..cbbd3ad4b --- /dev/null +++ b/lib/internal/promiseCallback.js @@ -0,0 +1,24 @@ +import noop from 'lodash/noop'; + +var supportsPromise = typeof Promise === 'function'; + +export default supportsPromise ? promiseCallback : noopCallback; + +function noopCallback() { + return noop; +} + +function promiseCallback() { + var resolve, reject; + function callback(err, value) { + if (err) return reject(err); + resolve(value); + } + + callback.promise = new Promise(function (res, rej) { + resolve = res; + reject = rej; + }) + + return callback; +} diff --git a/mocha_test/es2017/asyncFunctions.js b/mocha_test/es2017/asyncFunctions.js index ca76a56c9..8f77bdc1d 100644 --- a/mocha_test/es2017/asyncFunctions.js +++ b/mocha_test/es2017/asyncFunctions.js @@ -2,6 +2,7 @@ var async = require('../../lib'); const expect = require('chai').expect; const assert = require('assert'); +const promiseProto = Object.getPrototypeOf(new Promise(()=>{})); module.exports = function () { async function asyncIdentity(val) { @@ -32,8 +33,16 @@ module.exports = function () { * Collections */ - it('should handle async functions in each', (done) => { - async.each(input, asyncIdentity, done); + it('should handle async functions in each', async () => { + var promise = async.each(input, asyncIdentity); + assert(typeof promise.then === 'function'); + assert(Object.getPrototypeOf(promise) === promiseProto); + await promise; + }); + + it('should handle async functions in each (empty array)', async () => { + var promise = async.each([], asyncIdentity); + await promise; }); it('should handle async functions in eachLimit', (done) => { @@ -56,11 +65,9 @@ module.exports = function () { async.eachOfSeries(input, asyncIdentity, done); }); - it('should handle async functions in map', (done) => { - async.map(input, asyncIdentity, (err, result) => { - expect(result).to.eql(input); - done(err); - }); + it('should handle async functions in map', async () => { + var result = await async.map(input, asyncIdentity); + expect(result).to.eql(input); }); it('should handle async functions in mapLimit', (done) => {