Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Implement async / await (and Stream variants) #221

Closed
ochafik opened this issue Jun 16, 2015 · 10 comments
Closed

Implement async / await (and Stream variants) #221

ochafik opened this issue Jun 16, 2015 · 10 comments

Comments

@ochafik
Copy link
Contributor

ochafik commented Jun 16, 2015

Looks like this should be relatively easy with ES6 generators:

f() async {
  try {
    final value = await g();
    return 'Result: ' + (value + 1);
  } catch (e) {
    if (e == 'rethrow me') throw e;
    return 'Caught: ' + e;
  }
}

Could be transpiled to something like:

var f = (g) => dart.consumeAwaits((function*() {
  var callback$1;
  try {
    var value = ((yield new dart.Await(g(), cb => callback$1 = cb)) || callback$1());
    return 'Result: ' + (value + 1);
  } catch (e) {
    if (e == 'rethrow me') throw e;
    return 'Caught: ' + e;
  }
})());

With a helper along these lines:

dart.consumeAwaits = function(iterator) {
  try {
    let result = iterator.next();
    if (result.value instanceof dart.Await) {
      let await = result.value;
      var nextPromise = await.promise
        .then(
            v => await.callback(() => v),
            e => await.callback(() => { throw e }));
      return result.done ? nextPromise : nextPromise.then(() => dart.consumeAwaits(iterator));
    } else {
      if (!result.done) {
        throw new Error('Invalid state: not expecting non-dart.Await values before the iterator is done.');
      }
      return dart.Future.value(result.value);
    }
  } catch (e) {
    return dart.Future.error(e);
  }
};

Another option would be to dig the defunct async/await pub transformer from the grave, but it might produce less ES6-idiomatic code?

@kmillikin
Copy link

I would recommend a native approach, not the async/await transformer. The transformer goes through a lot of effort to (1) make synchronous code fully synchronous, (2) use constant space for synchronous looping via a trampoline, and (3) avoid a space leak in the dart Future implementation when awaiting in a loop. There is also a lot of duplicated exception handling code (e.g., around every callback body).

All of that should be avoidable with a native encoding as a generator of futures, such as the one you sketch.

@ochafik
Copy link
Contributor Author

ochafik commented Jun 16, 2015

Here's a complete example that runs on io.js.

yield doesn't seem to cross the boundary of local functions, so I've introduced a synthetic callback var and I leverage the fact that yield returns undefined to chain it with the callback.

var f = (g) => dart.consumeAwaits((function*() {
  var callback$1;
  try {
    var value = ((yield new dart.Await(g(), cb => callback$1 = cb)) || callback$1());
    return 'Result: ' + (value + 1);
  } catch (e) {
    if (e == 'rethrow me') throw e;
    return 'Caught: ' + e;
  }
})());

@jmesserly
Copy link
Contributor

DDC doesn't type check async either: #151

There's another option for implementation: dart2js has a transform that operates on their JS trees (which DDC is using an older version of + ES6 nodes in ours). https://github.com/dart-lang/sdk/blob/master/pkg/compiler/lib/src/js/rewrite_async.dart ... That would be more relevant to generating ES5 though.

But yeah, for ES6, expressing it on top of generators is almost certainly the way to go. Anything that avoids building the state machine is going to be more readable :). There's a lot of subtleties to think through though.

@jmesserly jmesserly self-assigned this Jun 22, 2015
@jmesserly
Copy link
Contributor

taking a look at this soon. More stuff is depending on it (including package:test now) so we aren't going to get very far without it.

@vsmenon
Copy link
Contributor

vsmenon commented Jun 22, 2015

#151 is now fixed. We still have a deeper typing issue with #228, but I think we figure that out in parallel with codegen.

@jmesserly
Copy link
Contributor

awesome, thanks Vijay!

@jmesserly
Copy link
Contributor

almost ready to send initial sync* variant (that's the easy one :) ) ... but test caught an analyzer bug: https://codereview.chromium.org/1213503002/, not sure if there's an easy way to workaround.

@jmesserly
Copy link
Contributor

https://codereview.chromium.org/1207313002/
edit: just put a TODO in the test, and added a cast explicitly to avoid the refiy coercion/node replacer bug

@jmesserly
Copy link
Contributor

I'm tempted to use the co library: https://github.com/tj/co, which adapts JS generators to Promises. But we'd need to solve #245 first.

edit: co is interesting because it's used as a polyfill for potential ES7 async/await https://github.com/lukehoban/ecmascript-asyncawait

jmesserly pushed a commit that referenced this issue Jun 26, 2015
Right now it maps straight to ES6 generators. This should handle some basic cases, but there's more work to get all of the corners working. For example: it doesn't implement Iterable yet.

R=vsm@google.com

Review URL: https://codereview.chromium.org/1207313002.
@jmesserly
Copy link
Contributor

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

No branches or pull requests

4 participants