Skip to content

async function support#1390

Merged
aearly merged 17 commits intomasterfrom
async-fn-support
Apr 2, 2017
Merged

async function support#1390
aearly merged 17 commits intomasterfrom
async-fn-support

Conversation

@aearly
Copy link
Copy Markdown
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
Copy Markdown
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.

Comment thread .travis.yml Outdated
# - node_js: "6"
# addons:
# firefox: "49.0"
# env: BROWSER=true MAKE_TEST=true
Copy link
Copy Markdown
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
Copy Markdown
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).

Comment thread lib/autoInject.js Outdated
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
Copy Markdown
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
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤢

Copy link
Copy Markdown
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.

Comment thread lib/doWhilst.js
@@ -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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

Comment thread lib/internal/applyEach.js Outdated
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
Copy Markdown
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

Comment thread lib/internal/applyEach.js Outdated
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
Copy Markdown
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
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good call. 👍

Comment thread lib/internal/wrapAsync.js
var supported;
try {
/* eslint no-eval: 0 */
supported = isAsync(eval('(async function () {})'));
Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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! 😄

Comment thread lib/asyncify.js Outdated
* ], callback);
*
* // es6 example
* // es2017 example
Copy link
Copy Markdown
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
Copy Markdown
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.

Comment thread lib/doWhilst.js
@@ -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
Copy Markdown
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?

Comment thread lib/internal/wrapAsync.js
var supported;
try {
/* eslint no-eval: 0 */
supported = isAsync(eval('(async function () {})'));
Copy link
Copy Markdown
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.

Comment thread 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
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Comment thread 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
Copy Markdown
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?

Comment thread 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
Copy Markdown
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
Copy Markdown
Collaborator Author

aearly commented Apr 1, 2017

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

Comment thread .eslintrc
},
"parserOptions": {
"ecmaVersion": 6,
"ecmaVersion": 8,
Copy link
Copy Markdown
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
Copy Markdown
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. 😛

Comment thread package.json
"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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

4 participants