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

async function support #1390

Merged
merged 17 commits into from Apr 2, 2017
Merged
Show file tree
Hide file tree
Changes from 13 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
2 changes: 1 addition & 1 deletion .eslintrc
Expand Up @@ -6,7 +6,7 @@
"es6": true
},
"parserOptions": {
"ecmaVersion": 6,
"ecmaVersion": 8,
Copy link
Collaborator

Choose a reason for hiding this comment

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

what is es8? can we set this to 2017/2018 whatever?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ES8 is eslint's shorthand for ES2017. No, you can't use "2017"... Just subtract 2009. 😛

"sourceType": "module"
},
"rules": {
Expand Down
8 changes: 5 additions & 3 deletions .travis.yml
Expand Up @@ -4,12 +4,14 @@ node_js:
- "0.10"
- "0.12"
- "4"
- "6"
- "7"

matrix:
include:
- node_js: "6"
- node_js: "7"
addons:
firefox: "49.0"
firefox: "52.0"
env: BROWSER=true MAKE_TEST=true
env:
matrix: BROWSER=false MAKE_TEST=false
Expand All @@ -27,4 +29,4 @@ script:
# ensure buildable
- "[ $MAKE_TEST == false ] || make"
# test in firefox
- "[ $BROWSER == false ] || npm run mocha-browser-test"
- "[ $BROWSER == false ] || npm run mocha-browser-test"
188 changes: 106 additions & 82 deletions dist/async.js
Expand Up @@ -887,6 +887,105 @@ function _eachOfLimit(limit) {
};
}

/**
* Take a sync function and make it async, passing its return value to a
* callback. This is useful for plugging sync functions into a waterfall,
* series, or other async functions. Any arguments passed to the generated
* function will be passed to the wrapped function (except for the final
* callback argument). Errors thrown will be passed to the callback.
*
* If the function passed to `asyncify` returns a Promise, that promises's
* resolved/rejected state will be used to call the callback, rather than simply
* the synchronous return value.
*
* This also means you can asyncify ES2016 `async` functions.
*
* @name asyncify
* @static
* @memberOf module:Utils
* @method
* @alias wrapSync
* @category Util
* @param {Function} func - The synchronous function to convert to an
* asynchronous function.
* @returns {Function} An asynchronous wrapper of the `func`. To be invoked with
* (callback).
* @example
*
* // passing a regular synchronous function
* async.waterfall([
* async.apply(fs.readFile, filename, "utf8"),
* async.asyncify(JSON.parse),
* function (data, next) {
* // data is the result of parsing the text.
* // If there was a parsing error, it would have been caught.
* }
* ], callback);
*
* // passing a function returning a promise
* async.waterfall([
* async.apply(fs.readFile, filename, "utf8"),
* async.asyncify(function (contents) {
* return db.model.create(contents);
* }),
* function (model, next) {
* // `model` is the instantiated model object.
* // If there was an error, this function would be skipped.
* }
* ], callback);
*
* // es2017 example
* var q = async.queue(async.asyncify(async function(file) {
* var intermediateStep = await processFile(file);
* return await somePromise(intermediateStep)
* }));
*
* q.push(files);
*/
function asyncify(func) {
return initialParams(function (args, callback) {
var result;
try {
result = func.apply(this, args);
} catch (e) {
return callback(e);
}
// if result is Promise object
if (isObject(result) && typeof result.then === 'function') {
result.then(function (value) {
callback(null, value);
}, function (err) {
callback(err.message ? err : new Error(err));
});
} else {
callback(null, result);
}
});
}

var supportsSymbol = typeof Symbol !== 'undefined';

function supportsAsync() {
var supported;
try {
/* eslint no-eval: 0 */
supported = supportsSymbol && isAsync(eval('(async function () {})'));
} catch (e) {
supported = false;
}
return supported;
}

function isAsync(fn) {
return fn[Symbol.toStringTag] === 'AsyncFunction';
}

var wrapAsync$1 = supportsAsync() ? function wrapAsync(asyncFn) {
if (!supportsSymbol) return asyncFn;

return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn;
} : identity;

/**
* The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a
* time.
Expand All @@ -910,7 +1009,7 @@ function _eachOfLimit(limit) {
* `iteratee` functions have finished, or an error occurs. Invoked with (err).
*/
function eachOfLimit(coll, limit, iteratee, callback) {
_eachOfLimit(limit)(coll, iteratee, callback);
_eachOfLimit(limit)(coll, wrapAsync$1(iteratee), callback);
}

function doLimit(fn, limit) {
Expand Down Expand Up @@ -988,7 +1087,7 @@ var eachOfGeneric = doLimit(eachOfLimit, Infinity);
*/
var eachOf = function (coll, iteratee, callback) {
var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric;
eachOfImplementation(coll, iteratee, callback);
eachOfImplementation(coll, wrapAsync$1(iteratee), callback);
};

function doParallel(fn) {
Expand All @@ -1002,10 +1101,11 @@ function _asyncMap(eachfn, arr, iteratee, callback) {
arr = arr || [];
var results = [];
var counter = 0;
var _iteratee = wrapAsync$1(iteratee);

eachfn(arr, function (value, _, callback) {
var index = counter++;
iteratee(value, function (err, v) {
_iteratee(value, function (err, v) {
results[index] = v;
callback(err);
});
Expand Down Expand Up @@ -1205,82 +1305,6 @@ var apply$2 = rest(function (fn, args) {
});
});

/**
* Take a sync function and make it async, passing its return value to a
* callback. This is useful for plugging sync functions into a waterfall,
* series, or other async functions. Any arguments passed to the generated
* function will be passed to the wrapped function (except for the final
* callback argument). Errors thrown will be passed to the callback.
*
* If the function passed to `asyncify` returns a Promise, that promises's
* resolved/rejected state will be used to call the callback, rather than simply
* the synchronous return value.
*
* This also means you can asyncify ES2016 `async` functions.
*
* @name asyncify
* @static
* @memberOf module:Utils
* @method
* @alias wrapSync
* @category Util
* @param {Function} func - The synchronous function to convert to an
* asynchronous function.
* @returns {Function} An asynchronous wrapper of the `func`. To be invoked with
* (callback).
* @example
*
* // passing a regular synchronous function
* async.waterfall([
* async.apply(fs.readFile, filename, "utf8"),
* async.asyncify(JSON.parse),
* function (data, next) {
* // data is the result of parsing the text.
* // If there was a parsing error, it would have been caught.
* }
* ], callback);
*
* // passing a function returning a promise
* async.waterfall([
* async.apply(fs.readFile, filename, "utf8"),
* async.asyncify(function (contents) {
* return db.model.create(contents);
* }),
* function (model, next) {
* // `model` is the instantiated model object.
* // If there was an error, this function would be skipped.
* }
* ], callback);
*
* // es6 example
* var q = async.queue(async.asyncify(async function(file) {
* var intermediateStep = await processFile(file);
* return await somePromise(intermediateStep)
* }));
*
* q.push(files);
*/
function asyncify(func) {
return initialParams(function (args, callback) {
var result;
try {
result = func.apply(this, args);
} catch (e) {
return callback(e);
}
// if result is Promise object
if (isObject(result) && typeof result.then === 'function') {
result.then(function (value) {
callback(null, value);
}, function (err) {
callback(err.message ? err : new Error(err));
});
} else {
callback(null, result);
}
});
}

/**
* A specialized version of `_.forEach` for arrays without support for
* iteratee shorthands.
Expand Down Expand Up @@ -3083,7 +3107,7 @@ function _withoutIndex(iteratee) {
* });
*/
function eachLimit(coll, iteratee, callback) {
eachOf(coll, _withoutIndex(iteratee), callback);
eachOf(coll, _withoutIndex(wrapAsync$1(iteratee)), callback);
}

/**
Expand All @@ -3108,7 +3132,7 @@ function eachLimit(coll, iteratee, callback) {
* `iteratee` functions have finished, or an error occurs. Invoked with (err).
*/
function eachLimit$1(coll, limit, iteratee, callback) {
_eachOfLimit(limit)(coll, _withoutIndex(iteratee), callback);
_eachOfLimit(limit)(coll, _withoutIndex(wrapAsync$1(iteratee)), callback);
}

/**
Expand Down Expand Up @@ -3318,7 +3342,7 @@ function filterGeneric(eachfn, coll, iteratee, callback) {

function _filter(eachfn, coll, iteratee, callback) {
var filter = isArrayLike(coll) ? filterArray : filterGeneric;
filter(eachfn, coll, iteratee, callback || noop);
filter(eachfn, coll, wrapAsync$1(iteratee), callback || noop);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/asyncify.js
Expand Up @@ -48,7 +48,7 @@ import initialParams from './internal/initialParams';
* }
* ], callback);
*
* // es6 example
* // es2017 example
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it be useful to add a note here that async functions are supported out of the box (when not transpiled)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, we should clarify here.

* var q = async.queue(async.asyncify(async function(file) {
* var intermediateStep = await processFile(file);
* return await somePromise(intermediateStep)
Expand Down
3 changes: 2 additions & 1 deletion lib/auto.js
Expand Up @@ -8,6 +8,7 @@ import rest from './internal/rest';

import once from './internal/once';
import onlyOnce from './internal/onlyOnce';
import wrapAsync from './internal/wrapAsync';

/**
* Determines the best order for running the functions in `tasks`, based on
Expand Down Expand Up @@ -213,7 +214,7 @@ export default function (tasks, concurrency, callback) {
}));

runningTasks++;
var taskFn = task[task.length - 1];
var taskFn = wrapAsync(task[task.length - 1]);
if (task.length > 1) {
taskFn(results, taskCallback);
} else {
Expand Down
17 changes: 12 additions & 5 deletions lib/autoInject.js
Expand Up @@ -3,8 +3,10 @@ import forOwn from 'lodash/_baseForOwn';
import arrayMap from 'lodash/_arrayMap';
import isArray from 'lodash/isArray';
import trim from 'lodash/trim';
import wrapAsync from './internal/wrapAsync';
import { isAsync } from './internal/wrapAsync';

var FN_ARGS = /^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARGS = /^(?:async\s+)?(function)?\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /(=.+)?(\s*)$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
Expand Down Expand Up @@ -106,22 +108,27 @@ export default function autoInject(tasks, callback) {

forOwn(tasks, function (taskFn, key) {
var params;
var fnIsAsync = isAsync(taskFn);
var hasNoDeps =
(!fnIsAsync && taskFn.length === 1) ||
(fnIsAsync && taskFn.length === 0);

if (isArray(taskFn)) {
params = taskFn.slice(0, -1);
taskFn = taskFn[taskFn.length - 1];

newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn);
} else if (taskFn.length === 1) {
} else if (hasNoDeps) {
// no dependencies, use the function as-is
newTasks[key] = taskFn;
} else {
params = parseParams(taskFn);
if (taskFn.length === 0 && params.length === 0) {
if (taskFn.length === 0 && !fnIsAsync && params.length === 0) {
throw new Error("autoInject task functions require explicit parameters.");
}

params.pop();
// remove callback param
if (!fnIsAsync) params.pop();

newTasks[key] = params.concat(newTask);
}
Expand All @@ -131,7 +138,7 @@ export default function autoInject(tasks, callback) {
return results[name];
});
newArgs.push(taskCb);
taskFn.apply(null, newArgs);
wrapAsync(taskFn).apply(null, newArgs);
}
});

Expand Down
7 changes: 5 additions & 2 deletions lib/doDuring.js
@@ -1,6 +1,7 @@
import noop from 'lodash/noop';
import rest from './internal/rest';
import onlyOnce from './internal/onlyOnce';
import wrapAsync from './internal/wrapAsync';

/**
* The post-check version of [`during`]{@link module:ControlFlow.during}. To reflect the difference in
Expand All @@ -25,17 +26,19 @@ import onlyOnce from './internal/onlyOnce';
*/
export default function doDuring(fn, test, callback) {
callback = onlyOnce(callback || noop);
var _fn = wrapAsync(fn);
var _test = wrapAsync(test);

var next = rest(function(err, args) {
if (err) return callback(err);
args.push(check);
test.apply(this, args);
_test.apply(this, args);
});

function check(err, truth) {
if (err) return callback(err);
if (!truth) return callback(null);
fn(next);
_fn(next);
}

check(null, true);
Expand Down