Skip to content

Commit

Permalink
Merge pull request #1390 from caolan/async-fn-support
Browse files Browse the repository at this point in the history
`async` function support
  • Loading branch information
aearly authored Apr 2, 2017
2 parents 66b3c72 + 8faed87 commit 49119a8
Show file tree
Hide file tree
Showing 86 changed files with 1,256 additions and 417 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"es6": true
},
"parserOptions": {
"ecmaVersion": 6,
"ecmaVersion": 8,
"sourceType": "module"
},
"rules": {
Expand Down
8 changes: 5 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 49119a8

Please sign in to comment.