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

async function support #1390

merged 17 commits into from Apr 2, 2017

Conversation

aearly
Copy link
Collaborator

@aearly aearly commented Mar 25, 2017

#1386

This adds support for passing async functions wherever it might make sense.

Obviously we are duplicating some core functionality with things like map and race, but this allows us to treat things uniformly. We do have some good value to add with functions that limit concurrency, and adding more higher-order operations that just map and each (as you would get with Promise.map() and Promise.all()). "Lodash for async functions" is a good thing to strive to be.

Most of the control flow functions are of dubious value, (waterfall, parallel, whilst), but you can do some really neat things with auto and queue. (I really love the autoInject shorthand test!)

The utility functions are a bit strange. retry, reflect, memoize and timeout now take in an async function and return a callback-accepting function. It feels like those should create a Promise-returning function.

@aearly
Copy link
Collaborator Author

aearly commented Mar 25, 2017

Also, I ran the benchmarks -- there is no difference in performance, whether the environment supports async functions or not.

.travis.yml Outdated
# - node_js: "6"
# addons:
# firefox: "49.0"
# env: BROWSER=true MAKE_TEST=true
Copy link
Collaborator

Choose a reason for hiding this comment

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

why are we disabling this stuff? Can we just up the firefox version?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I disabled this thinking it was an issue with karma, but it's a syntax error. I'll investigate further and re-enable. We could test both 45 (ESR, no async support) and 52 (async supported).


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 ((!fnIsAsync && taskFn.length === 1) ||
(fnIsAsync && taskFn.length === 0)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

fnIsAsync + taskFn.length === 1 =P (dont do it lol)

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 Author

Choose a reason for hiding this comment

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

Actually, I should probably factor this condition out into a hasNoDeps variable.

lib/doWhilst.js Outdated
@@ -19,7 +20,7 @@ import onlyOnce from './internal/onlyOnce';
* passes. The function is passed a `callback(err)`, which must be called once
* it has completed with an optional `err` argument. Invoked with (callback).
* @param {Function} test - synchronous truth test to perform after each
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a AsyncFunction jsdoc tag?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was thinking about the best way to document this. For sure, add something to the intro.md, but we have lots of repeated docs where we describe the iteratee function as a node-style async function. It would be nice to centralize the description of what Async accepts, with each Async method just describing the non-callback parameters.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps an external definition if jsdoc doesn't support some way of documenting an async function?

http://usejsdoc.org/tags-external.html

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm also thinking of using a @typedef, like we do for the return values of queue/cargo.

http://usejsdoc.org/tags-typedef.html

Copy link
Collaborator

Choose a reason for hiding this comment

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

I really don't like how the @typedefs look in our code. I'd rather just link to MDN

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Check the latest commit. It's cleaner than the JSDoc docs make it out to be...

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 for using typedef for this. Does this mean most of the docs should to be updated to reflect that iteratee should be an AsyncFunction type?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, lots of docs to update. 😓 It does create a nice link to the central AsyncFunction docs in the rendered docs, though.


export default function applyEach(eachfn) {
return rest(function(fns, args) {
var go = initialParams(function(args, callback) {
var that = this;
return eachfn(fns, function (fn, cb) {
return eachfn(arrayMap(fns, wrapAsync), function (fn, cb) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

fns can be a array|object|iterable


export default function applyEach(eachfn) {
return rest(function(fns, args) {
var go = initialParams(function(args, callback) {
var that = this;
return eachfn(fns, function (fn, cb) {
return eachfn(arrayMap(fns, wrapAsync), function (fn, cb) {
fn.apply(that, args.concat(cb));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do wrapAsync here instead

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good call. 👍

var supported;
try {
/* eslint no-eval: 0 */
supported = isAsync(eval('(async function () {})'));
Copy link

Choose a reason for hiding this comment

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

Eval is evil. Why not Function?

supported = isAsync(new Function("return (async function () {})")());

Copy link
Collaborator

Choose a reason for hiding this comment

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

eval can be abused in the same way that new Function can be abused. I'd rather just use eval here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

new Function() and eval are equivalently evil. Here, they are necessary, because there is no other way to detect async support without causing a parse error that halts JS execution.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm good with using eval here instead of new Function.

Copy link
Collaborator

@hargasinski hargasinski left a comment

Choose a reason for hiding this comment

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

I'm really excited for this, awesome work! 😄

lib/asyncify.js Outdated
@@ -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.

lib/doWhilst.js Outdated
@@ -19,7 +20,7 @@ import onlyOnce from './internal/onlyOnce';
* passes. The function is passed a `callback(err)`, which must be called once
* it has completed with an optional `err` argument. Invoked with (callback).
* @param {Function} test - synchronous truth test to perform after each
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 for using typedef for this. Does this mean most of the docs should to be updated to reflect that iteratee should be an AsyncFunction type?

var supported;
try {
/* eslint no-eval: 0 */
supported = isAsync(eval('(async function () {})'));
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm good with using eval here instead of new Function.

lib/index.js Outdated
* This type of function is also referred to as a "Node-style async function".
*
* Wherever we accept a Node-style async function, we also directly accept an
* ES2017 `async` function.
Copy link
Collaborator

Choose a reason for hiding this comment

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

lib/index.js Outdated
* ES2017 `async` function.
* In this case, the `async` function will not be passed a callback, and any
* thrown error will be used as the `err` argument of a theoretical callback,
* and the return value will be used as the `result` value.
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do you mean the async function will not be passed a callback? Do you mean the result of the async function?

lib/index.js Outdated
* thrown error will be used as the `err` argument of a theoretical callback,
* and the return value will be used as the `result` value.
*
* Note that we can only detect native `async function`s in this case.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Note, due to JavaScript limitations, we can only detect native async functions and not polyfilled implementations.

@aearly
Copy link
Collaborator Author

aearly commented Apr 1, 2017

@megawac @hargasinski Many doc updates for your 👀. 😓

@@ -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. 😛

"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-istanbul": "^2.0.1",
"babel-plugin-transform-es2015-modules-commonjs": "^6.3.16",
"babel-preset-es2015": "^6.3.13",
"babel-preset-es2017": "^6.22.0",
Copy link
Collaborator

Choose a reason for hiding this comment

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

are we using this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, otherwise babel can't parse the test file where we use async functions, even if they're being used natively.

@megawac
Copy link
Collaborator

megawac commented Apr 2, 2017

👏 awesome work, pretty excited for this

@aearly aearly merged commit 49119a8 into master Apr 2, 2017
@aearly aearly mentioned this pull request Apr 2, 2017
@aearly aearly deleted the async-fn-support branch April 2, 2017 22:37
@hargasinski
Copy link
Collaborator

I'm a little late, but really great work. I'm excited about this 💯

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants