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

Transform async functions and await expressions #101

Merged
merged 4 commits into from
Sep 11, 2014
Merged

Conversation

benjamn
Copy link
Collaborator

@benjamn benjamn commented Apr 3, 2014

This functionality depends on my async-await branch of the Esprima parser (pull request: https://github.com/ariya/esprima/pull/234).

The wrapGenerator.async function is heavily inspired by @lukehoban's spawn function: https://github.com/lukehoban/ecmascript-asyncawait#spawning

Note that Promise is left as a free variable, so you have to bring your own Promise polyfill.

Adding a bunch of tests for this functionality is my next order of business.

cc @amasad @thomasboyt @subtleGradient @bklimt @lukehoban @arv

@benjamn
Copy link
Collaborator Author

benjamn commented Apr 3, 2014

And here's an example of the transform in action (borrowed from here):

async function chainAnimationsAsync(elem, animations) {
  var ret = null;
  try {
    for (var anim in animations) {
      ret = await anim(elem);
    }
  } catch(e) { /* ignore and keep going */ }
  return ret;
}

becomes

function chainAnimationsAsync(elem, animations) {
  var ret, anim;

  return wrapGenerator.async(function chainAnimationsAsync$($ctx0) {
    while (1) switch ($ctx0.prev = $ctx0.next) {
    case 0:
      ret = null;
      $ctx0.prev = 1;
      $ctx0.t0 = $ctx0.keys(animations);
    case 3:
      if (($ctx0.t1 = $ctx0.t0()).done) {
        $ctx0.next = 10;
        break;
      }

      anim = $ctx0.t1.value;
      $ctx0.next = 7;
      return anim(elem);
    case 7:
      ret = $ctx0.sent;
      $ctx0.next = 3;
      break;
    case 10:
      $ctx0.next = 14;
      break;
    case 12:
      $ctx0.prev = 12;
      $ctx0.t2 = $ctx0.catch(1);
    case 14:
      return $ctx0.abrupt("return", ret);
    case 15:
    case "end":
      return $ctx0.stop();
    }
  }, this, [[1, 12]]);
}

@benjamn
Copy link
Collaborator Author

benjamn commented Apr 3, 2014

Regenerator doesn't support arrow generator functions yet, because there's no agreed-upon syntax for them, but I definitely intend to add support for async arrow functions.


function step(arg) {
try {
var info = this(arg);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: calling this looks awkward, why not just bind to the first arg 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.

Just trying to shave bytes, that's all.

@amasad
Copy link
Contributor

amasad commented Apr 3, 2014

nice! I thought it's going to take a lot more code to implement.
Excited about this! 👍

@@ -15,7 +15,7 @@ var transform = require("./lib/visit").transform;
var utils = require("./lib/util");
var recast = require("recast");
var types = recast.types;
var genFunExp = /\bfunction\s*\*/;
var genOrAsyncFunExp = /\bfunction\s*\*|\basync\b/;
Copy link
Contributor

Choose a reason for hiding this comment

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

"async" is more likely to occur in JS codebases, e.g. the popular library. Maybe 'await' is better to check for?

Copy link

Choose a reason for hiding this comment

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

You can have an async function that has no awaits in it -- but you probably could get away with being more specific a la /\bfunction\s*\*|\basync\s+function\b/

@arv
Copy link

arv commented Apr 7, 2014

Very nice

@sophiebits
Copy link
Contributor

This looks so awesome.

@lukehoban
Copy link

This is awesome.

I tried a more complex example, and ran into a problem with the handling of this:

var headers;
async function foo() {
}

This compiles to the following:

var headers;
(function foo() {
  return wrapGenerator.async(function foo$($ctx0) {
    while (1) switch ($ctx0.prev = $ctx0.next) {
    case 0:
    case "end":
      return $ctx0.stop();
    }
  }, this);
})

Note the extra parentheses added around the function declaration, turning it into a function expression and not binding a global variable to the name foo.

This only happens when a statement like var headers appears before the function declaration.

I expect this is an issue in the esprima additions for await. I tried a similar example with generator functions and do not see this issue.

@lukehoban
Copy link

A larger sample working with this is here: https://github.com/lukehoban/ecmascript-asyncawait/blob/regenerator/server.asyncawait.js as part of a branch of the async/await proposal to try to switch to using this regenerator branch for the sample instead of sweet.js macros.

@benjamn
Copy link
Collaborator Author

benjamn commented Apr 8, 2014

Yep, I can confirm that this is related to my Esprima changes, and Recast is just trying to respect the wrong FunctionExpression type by adding the parentheses.

Will investigate.

@benjamn
Copy link
Collaborator Author

benjamn commented Apr 8, 2014

@lukehoban Fixed this bug by adding benjamn/esprima@b714306 to my https://github.com/ariya/esprima/pull/234 pull request. In short, I was misusing peekLineTerminator() in matchAsync.

I also switched getCollaboratorImages back to a function declaration in your server.asyncawait.js file, and the transformation did not add parentheses to it.

@benjamn
Copy link
Collaborator Author

benjamn commented Apr 12, 2014

@lukehoban where did TC39 come down on await*? The notes seem to indicate they don't want it if there's no analogy to yield*, which is fine with me.

@niieani
Copy link

niieani commented Aug 13, 2014

Hey everybody, great work @benjamn . I see no movement since April, how's the progress on this?
This is such a nice feature to have!

@benjamn benjamn force-pushed the async-await branch 2 times, most recently from 8f31787 to 5da67bc Compare September 11, 2014 06:44
benjamn added a commit that referenced this pull request Sep 11, 2014
Transform async functions and await expressions.
@benjamn benjamn merged commit b130d87 into master Sep 11, 2014
@sophiebits
Copy link
Contributor

Nice!

@jayphelps
Copy link

fuck yeah

matthewrobb referenced this pull request in resugar/resugar Sep 27, 2014
@chrisabrams
Copy link

👍

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

Successfully merging this pull request may close these issues.

9 participants