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 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
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/applyEach.js
Expand Up @@ -13,7 +13,7 @@ import map from './map';
* @memberOf module:ControlFlow
* @method
* @category Control Flow
* @param {Array|Iterable|Object} fns - A collection of asynchronous functions
* @param {Array|Iterable|Object} fns - A collection of {@link AsyncFunction}s
* to all call with the same arguments
* @param {...*} [args] - any number of separate arguments to pass to the
* function.
Expand Down
2 changes: 1 addition & 1 deletion lib/applyEachSeries.js
Expand Up @@ -10,7 +10,7 @@ import mapSeries from './mapSeries';
* @method
* @see [async.applyEach]{@link module:ControlFlow.applyEach}
* @category Control Flow
* @param {Array|Iterable|Object} fns - A collection of asynchronous functions to all
* @param {Array|Iterable|Object} fns - A collection of {@link AsyncFunction}s to all
* call with the same arguments
* @param {...*} [args] - any number of separate arguments to pass to the
* function.
Expand Down
13 changes: 7 additions & 6 deletions lib/asyncify.js
Expand Up @@ -12,18 +12,18 @@ import initialParams from './internal/initialParams';
* 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.
* This also means you can asyncify ES2017 `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).
* @param {Function} func - The synchronous funuction, or Promise-returning
* function to convert to an {@link AsyncFunction}.
* @returns {AsyncFunction} An asynchronous wrapper of the `func`. To be
* invoked with `(args..., callback)`.
* @example
*
* // passing a regular synchronous function
Expand All @@ -48,7 +48,8 @@ import initialParams from './internal/initialParams';
* }
* ], callback);
*
* // es6 example
* // es2017 example, though `asyncify` is not needed if your JS environment
* // supports async functions out of the box
* var q = async.queue(async.asyncify(async function(file) {
* var intermediateStep = await processFile(file);
* return await somePromise(intermediateStep)
Expand Down
11 changes: 6 additions & 5 deletions lib/auto.js
Expand Up @@ -8,19 +8,20 @@ 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
* Determines the best order for running the {@link AsyncFunction}s in `tasks`, based on
* their requirements. Each function can optionally depend on other functions
* being completed first, and each function is run as soon as its requirements
* are satisfied.
*
* If any of the functions pass an error to their callback, the `auto` sequence
* If any of the {@link AsyncFunction}s pass an error to their callback, the `auto` sequence
* will stop. Further tasks will not execute (so any other functions depending
* on it will not run), and the main `callback` is immediately called with the
* error.
*
* Functions also receive an object containing the results of functions which
* {@link AsyncFunction}s also receive an object containing the results of functions which
* have completed so far as the first argument, if they have dependencies. If a
* task function has no dependencies, it will only be passed a callback.
*
Expand All @@ -30,7 +31,7 @@ import onlyOnce from './internal/onlyOnce';
* @method
* @category Control Flow
* @param {Object} tasks - An object. Each of its properties is either a
* function or an array of requirements, with the function itself the last item
* function or an array of requirements, with the {@link AsyncFunction} itself the last item
* in the array. The object's key of a property serves as the name of the task
* defined by that property, i.e. can be used when specifying requirements for
* other tasks. The function receives one or two arguments:
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