Skip to content

Commit 49119a8

Browse files
authored
Merge pull request #1390 from caolan/async-fn-support
`async` function support
2 parents 66b3c72 + 8faed87 commit 49119a8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+1256
-417
lines changed

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"es6": true
77
},
88
"parserOptions": {
9-
"ecmaVersion": 6,
9+
"ecmaVersion": 8,
1010
"sourceType": "module"
1111
},
1212
"rules": {

.travis.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ node_js:
44
- "0.10"
55
- "0.12"
66
- "4"
7+
- "6"
8+
- "7"
79

810
matrix:
911
include:
10-
- node_js: "6"
12+
- node_js: "7"
1113
addons:
12-
firefox: "49.0"
14+
firefox: "52.0"
1315
env: BROWSER=true MAKE_TEST=true
1416
env:
1517
matrix: BROWSER=false MAKE_TEST=false
@@ -27,4 +29,4 @@ script:
2729
# ensure buildable
2830
- "[ $MAKE_TEST == false ] || make"
2931
# test in firefox
30-
- "[ $BROWSER == false ] || npm run mocha-browser-test"
32+
- "[ $BROWSER == false ] || npm run mocha-browser-test"

dist/async.js

Lines changed: 106 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,105 @@ function _eachOfLimit(limit) {
887887
};
888888
}
889889

890+
/**
891+
* Take a sync function and make it async, passing its return value to a
892+
* callback. This is useful for plugging sync functions into a waterfall,
893+
* series, or other async functions. Any arguments passed to the generated
894+
* function will be passed to the wrapped function (except for the final
895+
* callback argument). Errors thrown will be passed to the callback.
896+
*
897+
* If the function passed to `asyncify` returns a Promise, that promises's
898+
* resolved/rejected state will be used to call the callback, rather than simply
899+
* the synchronous return value.
900+
*
901+
* This also means you can asyncify ES2016 `async` functions.
902+
*
903+
* @name asyncify
904+
* @static
905+
* @memberOf module:Utils
906+
* @method
907+
* @alias wrapSync
908+
* @category Util
909+
* @param {Function} func - The synchronous function to convert to an
910+
* asynchronous function.
911+
* @returns {Function} An asynchronous wrapper of the `func`. To be invoked with
912+
* (callback).
913+
* @example
914+
*
915+
* // passing a regular synchronous function
916+
* async.waterfall([
917+
* async.apply(fs.readFile, filename, "utf8"),
918+
* async.asyncify(JSON.parse),
919+
* function (data, next) {
920+
* // data is the result of parsing the text.
921+
* // If there was a parsing error, it would have been caught.
922+
* }
923+
* ], callback);
924+
*
925+
* // passing a function returning a promise
926+
* async.waterfall([
927+
* async.apply(fs.readFile, filename, "utf8"),
928+
* async.asyncify(function (contents) {
929+
* return db.model.create(contents);
930+
* }),
931+
* function (model, next) {
932+
* // `model` is the instantiated model object.
933+
* // If there was an error, this function would be skipped.
934+
* }
935+
* ], callback);
936+
*
937+
* // es2017 example
938+
* var q = async.queue(async.asyncify(async function(file) {
939+
* var intermediateStep = await processFile(file);
940+
* return await somePromise(intermediateStep)
941+
* }));
942+
*
943+
* q.push(files);
944+
*/
945+
function asyncify(func) {
946+
return initialParams(function (args, callback) {
947+
var result;
948+
try {
949+
result = func.apply(this, args);
950+
} catch (e) {
951+
return callback(e);
952+
}
953+
// if result is Promise object
954+
if (isObject(result) && typeof result.then === 'function') {
955+
result.then(function (value) {
956+
callback(null, value);
957+
}, function (err) {
958+
callback(err.message ? err : new Error(err));
959+
});
960+
} else {
961+
callback(null, result);
962+
}
963+
});
964+
}
965+
966+
var supportsSymbol = typeof Symbol !== 'undefined';
967+
968+
function supportsAsync() {
969+
var supported;
970+
try {
971+
/* eslint no-eval: 0 */
972+
supported = supportsSymbol && isAsync(eval('(async function () {})'));
973+
} catch (e) {
974+
supported = false;
975+
}
976+
return supported;
977+
}
978+
979+
function isAsync(fn) {
980+
return fn[Symbol.toStringTag] === 'AsyncFunction';
981+
}
982+
983+
var wrapAsync$1 = supportsAsync() ? function wrapAsync(asyncFn) {
984+
if (!supportsSymbol) return asyncFn;
985+
986+
return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn;
987+
} : identity;
988+
890989
/**
891990
* The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a
892991
* time.
@@ -910,7 +1009,7 @@ function _eachOfLimit(limit) {
9101009
* `iteratee` functions have finished, or an error occurs. Invoked with (err).
9111010
*/
9121011
function eachOfLimit(coll, limit, iteratee, callback) {
913-
_eachOfLimit(limit)(coll, iteratee, callback);
1012+
_eachOfLimit(limit)(coll, wrapAsync$1(iteratee), callback);
9141013
}
9151014

9161015
function doLimit(fn, limit) {
@@ -988,7 +1087,7 @@ var eachOfGeneric = doLimit(eachOfLimit, Infinity);
9881087
*/
9891088
var eachOf = function (coll, iteratee, callback) {
9901089
var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric;
991-
eachOfImplementation(coll, iteratee, callback);
1090+
eachOfImplementation(coll, wrapAsync$1(iteratee), callback);
9921091
};
9931092

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

10061106
eachfn(arr, function (value, _, callback) {
10071107
var index = counter++;
1008-
iteratee(value, function (err, v) {
1108+
_iteratee(value, function (err, v) {
10091109
results[index] = v;
10101110
callback(err);
10111111
});
@@ -1205,82 +1305,6 @@ var apply$2 = rest(function (fn, args) {
12051305
});
12061306
});
12071307

1208-
/**
1209-
* Take a sync function and make it async, passing its return value to a
1210-
* callback. This is useful for plugging sync functions into a waterfall,
1211-
* series, or other async functions. Any arguments passed to the generated
1212-
* function will be passed to the wrapped function (except for the final
1213-
* callback argument). Errors thrown will be passed to the callback.
1214-
*
1215-
* If the function passed to `asyncify` returns a Promise, that promises's
1216-
* resolved/rejected state will be used to call the callback, rather than simply
1217-
* the synchronous return value.
1218-
*
1219-
* This also means you can asyncify ES2016 `async` functions.
1220-
*
1221-
* @name asyncify
1222-
* @static
1223-
* @memberOf module:Utils
1224-
* @method
1225-
* @alias wrapSync
1226-
* @category Util
1227-
* @param {Function} func - The synchronous function to convert to an
1228-
* asynchronous function.
1229-
* @returns {Function} An asynchronous wrapper of the `func`. To be invoked with
1230-
* (callback).
1231-
* @example
1232-
*
1233-
* // passing a regular synchronous function
1234-
* async.waterfall([
1235-
* async.apply(fs.readFile, filename, "utf8"),
1236-
* async.asyncify(JSON.parse),
1237-
* function (data, next) {
1238-
* // data is the result of parsing the text.
1239-
* // If there was a parsing error, it would have been caught.
1240-
* }
1241-
* ], callback);
1242-
*
1243-
* // passing a function returning a promise
1244-
* async.waterfall([
1245-
* async.apply(fs.readFile, filename, "utf8"),
1246-
* async.asyncify(function (contents) {
1247-
* return db.model.create(contents);
1248-
* }),
1249-
* function (model, next) {
1250-
* // `model` is the instantiated model object.
1251-
* // If there was an error, this function would be skipped.
1252-
* }
1253-
* ], callback);
1254-
*
1255-
* // es6 example
1256-
* var q = async.queue(async.asyncify(async function(file) {
1257-
* var intermediateStep = await processFile(file);
1258-
* return await somePromise(intermediateStep)
1259-
* }));
1260-
*
1261-
* q.push(files);
1262-
*/
1263-
function asyncify(func) {
1264-
return initialParams(function (args, callback) {
1265-
var result;
1266-
try {
1267-
result = func.apply(this, args);
1268-
} catch (e) {
1269-
return callback(e);
1270-
}
1271-
// if result is Promise object
1272-
if (isObject(result) && typeof result.then === 'function') {
1273-
result.then(function (value) {
1274-
callback(null, value);
1275-
}, function (err) {
1276-
callback(err.message ? err : new Error(err));
1277-
});
1278-
} else {
1279-
callback(null, result);
1280-
}
1281-
});
1282-
}
1283-
12841308
/**
12851309
* A specialized version of `_.forEach` for arrays without support for
12861310
* iteratee shorthands.
@@ -3083,7 +3107,7 @@ function _withoutIndex(iteratee) {
30833107
* });
30843108
*/
30853109
function eachLimit(coll, iteratee, callback) {
3086-
eachOf(coll, _withoutIndex(iteratee), callback);
3110+
eachOf(coll, _withoutIndex(wrapAsync$1(iteratee)), callback);
30873111
}
30883112

30893113
/**
@@ -3108,7 +3132,7 @@ function eachLimit(coll, iteratee, callback) {
31083132
* `iteratee` functions have finished, or an error occurs. Invoked with (err).
31093133
*/
31103134
function eachLimit$1(coll, limit, iteratee, callback) {
3111-
_eachOfLimit(limit)(coll, _withoutIndex(iteratee), callback);
3135+
_eachOfLimit(limit)(coll, _withoutIndex(wrapAsync$1(iteratee)), callback);
31123136
}
31133137

31143138
/**
@@ -3318,7 +3342,7 @@ function filterGeneric(eachfn, coll, iteratee, callback) {
33183342

33193343
function _filter(eachfn, coll, iteratee, callback) {
33203344
var filter = isArrayLike(coll) ? filterArray : filterGeneric;
3321-
filter(eachfn, coll, iteratee, callback || noop);
3345+
filter(eachfn, coll, wrapAsync$1(iteratee), callback || noop);
33223346
}
33233347

33243348
/**

lib/applyEach.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import map from './map';
1313
* @memberOf module:ControlFlow
1414
* @method
1515
* @category Control Flow
16-
* @param {Array|Iterable|Object} fns - A collection of asynchronous functions
16+
* @param {Array|Iterable|Object} fns - A collection of {@link AsyncFunction}s
1717
* to all call with the same arguments
1818
* @param {...*} [args] - any number of separate arguments to pass to the
1919
* function.

lib/applyEachSeries.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import mapSeries from './mapSeries';
1010
* @method
1111
* @see [async.applyEach]{@link module:ControlFlow.applyEach}
1212
* @category Control Flow
13-
* @param {Array|Iterable|Object} fns - A collection of asynchronous functions to all
13+
* @param {Array|Iterable|Object} fns - A collection of {@link AsyncFunction}s to all
1414
* call with the same arguments
1515
* @param {...*} [args] - any number of separate arguments to pass to the
1616
* function.

lib/asyncify.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,18 @@ import initialParams from './internal/initialParams';
1212
* resolved/rejected state will be used to call the callback, rather than simply
1313
* the synchronous return value.
1414
*
15-
* This also means you can asyncify ES2016 `async` functions.
15+
* This also means you can asyncify ES2017 `async` functions.
1616
*
1717
* @name asyncify
1818
* @static
1919
* @memberOf module:Utils
2020
* @method
2121
* @alias wrapSync
2222
* @category Util
23-
* @param {Function} func - The synchronous function to convert to an
24-
* asynchronous function.
25-
* @returns {Function} An asynchronous wrapper of the `func`. To be invoked with
26-
* (callback).
23+
* @param {Function} func - The synchronous funuction, or Promise-returning
24+
* function to convert to an {@link AsyncFunction}.
25+
* @returns {AsyncFunction} An asynchronous wrapper of the `func`. To be
26+
* invoked with `(args..., callback)`.
2727
* @example
2828
*
2929
* // passing a regular synchronous function
@@ -48,7 +48,8 @@ import initialParams from './internal/initialParams';
4848
* }
4949
* ], callback);
5050
*
51-
* // es6 example
51+
* // es2017 example, though `asyncify` is not needed if your JS environment
52+
* // supports async functions out of the box
5253
* var q = async.queue(async.asyncify(async function(file) {
5354
* var intermediateStep = await processFile(file);
5455
* return await somePromise(intermediateStep)

lib/auto.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@ import rest from './internal/rest';
88

99
import once from './internal/once';
1010
import onlyOnce from './internal/onlyOnce';
11+
import wrapAsync from './internal/wrapAsync';
1112

1213
/**
13-
* Determines the best order for running the functions in `tasks`, based on
14+
* Determines the best order for running the {@link AsyncFunction}s in `tasks`, based on
1415
* their requirements. Each function can optionally depend on other functions
1516
* being completed first, and each function is run as soon as its requirements
1617
* are satisfied.
1718
*
18-
* If any of the functions pass an error to their callback, the `auto` sequence
19+
* If any of the {@link AsyncFunction}s pass an error to their callback, the `auto` sequence
1920
* will stop. Further tasks will not execute (so any other functions depending
2021
* on it will not run), and the main `callback` is immediately called with the
2122
* error.
2223
*
23-
* Functions also receive an object containing the results of functions which
24+
* {@link AsyncFunction}s also receive an object containing the results of functions which
2425
* have completed so far as the first argument, if they have dependencies. If a
2526
* task function has no dependencies, it will only be passed a callback.
2627
*
@@ -30,7 +31,7 @@ import onlyOnce from './internal/onlyOnce';
3031
* @method
3132
* @category Control Flow
3233
* @param {Object} tasks - An object. Each of its properties is either a
33-
* function or an array of requirements, with the function itself the last item
34+
* function or an array of requirements, with the {@link AsyncFunction} itself the last item
3435
* in the array. The object's key of a property serves as the name of the task
3536
* defined by that property, i.e. can be used when specifying requirements for
3637
* other tasks. The function receives one or two arguments:
@@ -213,7 +214,7 @@ export default function (tasks, concurrency, callback) {
213214
}));
214215

215216
runningTasks++;
216-
var taskFn = task[task.length - 1];
217+
var taskFn = wrapAsync(task[task.length - 1]);
217218
if (task.length > 1) {
218219
taskFn(results, taskCallback);
219220
} else {

0 commit comments

Comments
 (0)