Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] POC for await-able Async functions #1526

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/each.js
Expand Up @@ -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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deliberately never returning anything from an Async function has given us the headroom to do this!

* @example
*
* // assuming openFiles is an array of file names and saveFile is a function
Expand Down Expand Up @@ -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);
}
8 changes: 5 additions & 3 deletions lib/eachOf.js
Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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"};
Expand All @@ -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);
}
3 changes: 2 additions & 1 deletion lib/eachOfLimit.js
Expand Up @@ -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);
}
5 changes: 3 additions & 2 deletions 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);
}
Expand Down Expand Up @@ -50,5 +50,6 @@ export default function _eachOfLimit(limit) {
}

replenish();
return callback.promise;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll want to track whether the callback is a user defined callback or an internal promise callback and only return promiseCallback.promise if its an internal version to avoid cases where users pass us a callback with some state on it.

};
}
7 changes: 5 additions & 2 deletions 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;
Expand All @@ -17,4 +18,6 @@ export default function _asyncMap(eachfn, arr, iteratee, callback) {
}, function (err) {
callback(err, results);
});

return callback.promise;
}
4 changes: 3 additions & 1 deletion 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;
Copy link
Collaborator

@megawac megawac Apr 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the wrapped function not return a promise instead to allow once(fn)(...args).then(...) or how do we intend this to work?

return wrapped;
}
24 changes: 24 additions & 0 deletions lib/internal/promiseCallback.js
@@ -0,0 +1,24 @@
import noop from 'lodash/noop';

var supportsPromise = typeof Promise === 'function';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A pretty crude way to detect promise support. This would be a 3.0 feature. I thinking of dropping support for non-ES2015 environments, in which case we could always return a Promise.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for dropping pre-ES2015 support in v3.


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;
}
21 changes: 14 additions & 7 deletions mocha_test/es2017/asyncFunctions.js
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simpler?

assert(promise instanceof Promise);

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) => {
Expand All @@ -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);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is sweet! It would be cool to also test async with generators, perhaps having a generator function that initializes some data for some of the test cases

expect(result).to.eql(input);
});

it('should handle async functions in mapLimit', (done) => {
Expand Down