Bringing async/await to life in JavaScript

My dream has come true this week. I can now write clean asynchronous code in JavaScript: no callbacks, no intrusive control flow library, no ugly preprocessor. Just plain JavaScript!

This is made possible by a new feature of JavaScript called generator functions, which has been introduced by EcmaScript 6 and is now available in node.js (unstable version 0.11.2). I already blogged about generators a few times so I won’t get into the basics again here. The important thing is that ES6 introduces two small extensions to the language syntax:

  • function*: the functions that you declare with a little twinkling star are generator functions. They execute in an unusual way and return generators.
  • yield: this keyword lets you transfer control from a generator to the function that controls it.

And, even though these two language constructs were not orginally designed to have the async/await semantics found in other languages, it is possible to give them these semantics:

  • The * in function* is your async keyword.
  • yield is your await keyword.

Knowing this, you can write asynchronous code as if JavaScript had async/await keywords. Here is an example:

function* countLines(path) {
    var names = yield fs.readdir(path);
    var total = 0;
    for (var i = 0; i < names.length; i++) {
        var fullname = path + '/' + names[i];
        var count = (yield fs.readFile(fullname, 'utf8')).split('\n').length;
        console.log(fullname + ': ' + count);
        total += count;
    }
    return total;
}

function* projectLineCounts() {
    var total = 0;
    total += yield countLines(__dirname + '/../examples');
    total += yield countLines(__dirname + '/../lib');
    total += yield countLines(__dirname + '/../test');
    console.log('TOTAL: ' + total);
    return total;
}

Here, we have two asynchronous functions (countLines and projectLineCounts) that call each other and call node.js APIs (fs.readdir, fs.readFile). If you look carefully you’ll notice that these functions don’t call any special async helper API. Everything is done with our two markers: the little * marks declarations of asynchronous functions and yield marks calls to asynchronous functions. Just like async and await in other languages.

And it will work!

Galaxy

The magic comes from galaxy, a small library that I derived from my earlier work on streamline.js and generators.

Part of the magic is that the fs variable is not the usual node.js file system module; it is a small wrapper around that module:

var galaxy = require('galaxy');
var fs = galaxy.star(require('fs'));

The galaxy.star function converts usual callback-based node.js functions into generator functions that play well with the generator functions that we have written above.

The other part of the magic comes from the galaxy.unstar function which converts in the other direction, from generator functions to callback-based node.js functions. This unstar function allows us to transform projectLineCounts into a callback-based function that we can call as a regular node.js function:

var projectLineCountsCb = galaxy.unstar(projectLineCounts);

projectLineCountsCb(function(err, result) {
    if (err) throw err;
    console.log('CALLBACK RESULT: ' + result);
});

The complete example is available here.

The whole idea behind this API design is that galaxy lets you write code in two different spaces:

  • The old callback space in which today’s node.js APIs live. In this space, you program with regular unstarred functions in continuation passing style (callbacks).
  • The new generator space. In this space, you program in synchronous style with starred functions.

The star and unstar functions allow you to expose the APIs of one space into the other space. And that’s all you need to bring async/await to life in node.js.

Status

I assembled galaxy quickly from pieces that I had developed for streamline.js. So it needs a bit of polishing and the API may move a bit. Generator support in V8 and node.js is also brand new. So all of this is not yet ready for prime time but you can already play with it if you are curious.

I have introduced a galaxy.spin function to parallelize function calls. I’ll probably carry over some other goodies from the streamline project (funnel semaphore, asynchronous array functions, streams module, …).

I find it exciting that modules written in async/await style with galaxy don’t have any direct dependencies on the node.js callback convention. So, for example, it would be easy to write a browser variant of the star/unstar functions which would be aligned on the jQuery callback conventions, with separate callback and errback.

Also, another module was announced on the node.js mailing list this week: suspend. It takes the problem from a slightly different angle, by wrapping every generator function with a suspend call. It lets you consume node.js APIs directly and write functions that follow node’s callback pattern. This is an attractive option for library developers who want to stay close to node’s callback model. Take a look at the source; it’s really clever: only 16 locs! Galaxy is different in that it moves you to a different space where you can program in sync style with no additional API, just language keywords. Probably a more attractive option if you are writing applications because you’ll get leaner code if most of your calls are to your own APIs rather than to node’s APIs.

Happy */yield coding!

About these ads
This entry was posted in Asynchronous JavaScript, Uncategorized. Bookmark the permalink.

41 Responses to Bringing async/await to life in JavaScript

  1. I had published a bit in rush yesterday. Just did a pass to fix some gliches and improve the end.

  2. Josh says:

    So what exactly is the final result in harmony itself going to be in terms of async/await-like functionality? Will libraries like galaxy/suspend or even Q’s .async() feature still be necessary to get the same C# feel?

  3. J says:

    Very interesting, thanks for the writeup. I am still a bit confused, and am not sure if I am understanding the concept correctly.

    Say I am trying to mimic a method
    sleep(50);
    is this possible? or am I thinking of something completely different?
    Something like this would work?
    var sleepA = galaxy.star(setTimeout, 0);
    var sleep = function(t){yield sleepA(t);}

  4. Your sleepA function is OK. But you would need to declare sleep as a function*:

    var sleep = function*(t) {
      yield sleepA(null, t);
    }
    

    And then, you cannot just call sleep as sleep(50), you have to yield on it:

    yield sleep(50);
    

    This implies that your code be inside a function*, not a function.

    So, going further, all the code that calls async functions will be inside function*, not regular functions. And all the calls to these functions will have a yield keyword in front of them.

    If you want to escape this function*/yield world and go back to the usual function world, you have to unstar the function. But then you get the continuation through a callback.

    So there is no magic here: galaxy gives you synchronous style programming as long as you write your code with starred functions and yield but you still have to use callbacks in regular, unstarred functions.

  5. J says:

    ahh ok, Thanks for the quick reply!
    So does this mean that all functions calling this Sleep() all the way up the stack all have to be function* ‘s (and use yield, or eventually use a callback)?
    I can see this being useful, but isn’t quite at the same point as C#’s await, which I guess is really what I am after. I guess I am more looking for the ECMA ‘deffered function’ implementation? that is still a way off.

    • J says:

      Hmm even looking at the deferred functions standard, is still isn’t really removing all the callbacks, or if it is I don’t understand what the purpose of ‘then(…)’ is?
      Why use the ‘then()’ callback method.
      Something like this is what would be nice (Similar to the syntax you get in c#):

      var c = await getXHRUsersCount();
      alert('user count is '+c);
      

      If you have to use then()…

      getXHRUsersCount().then(callback);
      

      It may as well just be

      getXHRUsersCount(callback);
      

      and there is no addition to the language there?
      I apologize for querying on something that may be trivial, I guess I am a huge fan of both JS and C# and like to see the best of C# go into js (I have been through callback hell in node) and really want to understand where ECMA is going.

      • Yes, that’s how it is. You have the choice between staying in the function*/yield world, or jumping back into callback-land. And the whole stack up is contaminated. The deferred proposal is similar. This is a because JavaScript only supports “single frame continuations”.

        But is it really a problem to write all the async calls with function* and yield?

        If you find this too clumsy, you have other options: node-fibers, which unleashes “deep continuations” or preprocessors like streamline.js.

      • Replying to your second part on deferred:

        You have two ways to use deferred functions:

        • you can call them with await. In this case you do not introduce any callback, you get the value directly, just like in C#.
        • you can call them without await. In this case you get a “deferred function” rather than the value computed by the call. You get the value through the then method and a callback.

        Galaxy is very similar to this model:

        • await is replaced by yield.
        • if you call without yield you get a generator. If you unstar this generator you can get the result with a callback.

        What I don’t see is how this is so different from C#: in C# you have to call Async functions with await and then you have to tag your function with async; and then your function is itself Async and must also be called with await so the caller must be tagged with async, etc, etc. The whole stack up is contaminated too and the coding overhead is the same.

      • J says:

        It is almost identical in c#, the difference I think being that you can have a single await statement in the middle of a synchronous method, in the JS version the method calling the yield appears to have to be a non-synchronous ( *function ) ( and all calls up the stack )

        So if I have a call large stack, I would have to ensure that all methods all the way up the stack all yield (In c# I don’t have to think about this?)

      • I did not test it but from what I have read on MSDN (http://msdn.microsoft.com/en-us/library/hh300224%28v=vs.110%29.aspx) you cannot use await inside a function which is not marked with async:

        3 – Because you added the Await or await operator in the previous step, a compiler error occurs. The operator can be used only in methods that are marked with the Async (Visual Basic) or async (C#) modifier. …/…

        4 – All that remains to be done in GetURLContents is to adjust the method signature. You can use the Await or await operator only in methods that are marked with the Async (Visual Basic) or async (C#) modifier. Add the modifier to mark the method as an async method, …/…

        This is the same constraint as in Galaxy.

        The difference is that .NET gives you APIs (task.WaitAndUnwrapException, AsyncContext.RunTask, see http://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c) that let you call async functions and get results from them inside a synchronous function. This allows you to stop the propagation. But you have to use these API calls, you cannot do it directly with the await keyword.

        Galaxy also allows you to invoke a function* from a sync (unstarred) function. You can do it by calling it as galaxy.spin(starredFn()) or as galaxy.unstar(starredFn)(callback). The big difference with C# is that these calls don’t wait for the completion of the async operation and that the async result (if any) is not available directly in the sync function’s scope, only in a callback scope.

  6. zenk says:

    Good news!

    Seems the same as generator in python, but different with coroutine in lua. If I want to use something like fiber or process in erlang, I have to write all the functions with star and yield.

  7. Paul Chen says:

    Just want to point out one typo:
    ◾The * in function* is your sync keyword.
    sync -> async

  8. Thanks for a nice blog post. I have also released a library at https://npmjs.org/package/yyield

    Since I believe we need more bridging, the Y-yield library supports quite a few notions of async code running to bridge generators. And easy to do parallel work too. Would love your feedback.

  9. Pingback: async/await replace in Nodejs - QueryPost.com | QueryPost.com

  10. Hello Bruno, great news!

    I just switched my server backend from streamline to suspend. I loved streamline, but sine it once again failed me on a good backtrace (catch and rethrowing errors destroyed the trace). Since I’ve not been watching this community for a while, I thought ECMA6 generators ought to have hit V8 by now. And they have :-) I expected them to be in node stable already, but I don’t an unstable release all that much.

    I tried galaxy at first, but it didn’t work well with node-mongodb-native which returns objects with callback API functions which are on the prototype chain instead of the object itself. suspend works fine with them tough.

    Looking at the changed code, I must say, streamline code looked way better. The underscore as magic yield token was all-round way more compact and expressive than yield, yield*, function*, suspend(), resume() and all that is required now. Also with streamline there was no difference in calling a function written with streamline and a native callback API function. Now I’ve also to differ to call with yield f(…, resume() ) for callback API and yield* f( ) when one generator calls another generator.

    Also the streamline preprocessor was way more exact in pointing out coding errors than working with generators. If I do these wrongly, it most often only results in a hang and a debugging session, while streamline most often pointed out the wrong semantics in start up phase.

    On the other hand, since generators are natively in V8 I always get complete and good backtraces / error messages and since it good the “blessing” of being called ECMA 6, its standard javascript instead of custom.

    In the afterthought with streamline I always felt on being on a aside of a small user base and thus having to deal with more early adopters bugs than with a product with a larger community behind it. Not that your dedication was always model and I could count on reported bugs being fixed within days. Too bad out of some reason streamline fell from nodes grace early on. Now we got pretty much the same idea with generators going to strike wide-spread use. It’s just IMHO not that elegant. However, I don’t mind all that much. And they got the force of ECMA and Google and all those behind them, that’s supposedly the reason there will not going be even close the same amount of resistance than streamline had.

    In an ideal world, V8 would have adopted streamline-like semantics natively. Maybe with * as yielding token.

  11. Hi Axel,

    You have to balance between the most compact/efficient syntax (streamline), and the strength of a standard (ES6 generators). It’s not obvious that ES6 generators are the end of the story anyway. My favorite proposal is the concurrency strawman (http://wiki.ecmascript.org/doku.php?id=strawman:concurrency). See the syntax proposal for “Eventual Operations”; it is very much in the streamline spirit (actually more powerful because it also supports async property accessors).

    On the other hand, the recent push for standardization of promises does not look too good to me. Why standardize bulky APIs that don’t really solve the problem (you still need a callback to get a result out of an async call). Would be much better to spend the energy on a tight powerful syntax (the ! of eventual operations).

    For our project, we chose to stick to streamline. We use the fibers runtime today but we may switch to generators if they catch up speed-wise (they do better than fibers on the memory side but they are slower). We actually use streamline’s “fast” mode, which will give us the best of both worlds (nice syntax and leanest possible generators code) if we switch to generators mode later.

  12. Richard says:

    This is great!

    However, while */yield is limited by existing Javascript rules, I don’t understand why C# async/await requires work from the programmer. I’d rather the async declarations be applied by the function internally to “await” and return to where it was called (without blocking the thread, of course). I’m not a compiler guy, but is there a good reason to make the programmer get the syntax right?

    For your */yield, forgetting a yield somewhere in node.js would block the entire process for the length of the operation would it not?

  13. The problem is in your “without blocking the thread” remark. If your function F1 doesn’t block the thread, you must give the thread something to grind while F1’s stack frames are still allocated on the thread’s stack. If you yield to another function F2, that function will start to stack some more stack frames and then it may call another async function F3. How do you return to F1 when F1’s I/O completes (it may complete before F3)? What do you do with the stack frames that have been pushed by F2? You have a stack management problem!

    That’s why, even in C#, you cannot just “hide” the yield and pretend the function is sync (remove the async or * marker from the function’s signature). Unless you accept to block the thread, but then you lose the benefits of async!

    If you forget a yield in galaxy, you won’t block the process. Instead, the function that you wanted to call will not run at all, and you will get the wrong value back. For example if F1 and F2 are two async functions that return strings and you write var foo = (yield F1()) + F2();, F1() will run as you expect and the subexpression (yield F1()) will return a string but F2() will not execute F2’s body. It will just return a generator object. So the result will be the string returned by F1 concatenated with "[object Generator]".

  14. Pingback: Google’s Dart Programming Language Is Coming To The Server | TechCrunch

  15. Pingback: Google’s Dart Programming Language Is Coming To The Server | geekville.com

  16. Pingback: Google’s Dart Programming Language Is Coming To The Server | daily1.com

  17. Pingback: Google I/O | Hihid News

  18. Pingback: Google’s Dart Programming Language Is Coming To The Server | Nagg

  19. Pingback: Google’s Dart Programming Language Is Coming To The Server | World Updates

  20. Pingback: Google’s Dart Programming Language Is Coming To The Server | i

  21. Pingback: INFUSION | Google’s Dart Programming Language Is Coming To The Server

  22. Pingback: Google’s Dart Programming Language Is Coming To The Server | Tech News Connect

  23. Pingback: Google’s Dart Programming Language Is Coming To The Server | TechNewsDB

  24. Pingback: Dartプログラミング言語をGoogleのApp Engineがサポート…ついにサーバ言語としても位置づけ – Techcrunch

  25. Pingback: Google’s Dart Programming Language Is Coming To The Server | Gath.co | IT, Design, Technology, Ventures, Engineering News

  26. Pingback: RK Solutions – Google’s Dart Programming Language Is Coming To The Server

  27. crunch483 says:

    Thanks very much for this library – looks great. I saw another library, “suspend”, mentioned in the comments. What should I consider when choosing between galaxy and suspend? Suspend has more thorough documentation but I’m sure galaxy must have its own advantages. Thanks.

    • One important thing to consider is whether you’ll be writing deep or shallow code.

      By deep I mean code which is dominated by functions that you wrote calling other functions that you wrote. By shallow I mean code which is dominated by interactions with native node.js APIs or 3rd party libraries.

      • suspend may be a better choice if your code is shallow because every function that you write will be exposed in callback style.
      • galaxy will shine if your code is deep because it will give you the leanest code: you don’t have to wrap your generator functions, you can call them directly from other generator functions.

      As always the best way to know is to experiment with both and pick what you feel more comfortable with.

      • crunch483 says:

        Thanks so much for the info. My main goal is to use a library like this in the Derby framework (http://derbyjs.com/) – I think I could just override the functions that accept route and controller callbacks to unstar() the callbacks so Galaxy could work quite well and let the programmer have a route or controller function that calls other functions without having to wrap each function.

        The bigger challenge is that Derby routes run on both the client and the server, and controllers run exclusively on the client, so I need a fallback solution that will work in browsers as well (except for Firefox which already supports generators), meaning I’ll need to use source code transformation. Traceur and Regenerator don’t work with Galaxy for some reason, but I had some initial success with the library mentioned here – http://stackoverflow.com/a/11315328/560114 – by modifying the source so it looks for ‘yield’ instead of ‘await’, making the code look equivalent to Galaxy. Unfortunately that library doesn’t seem to do error handling though (i.e. transforming try/catch to if (err) within the callback).

        Anyway, I’m rambling here, no need to reply to this unless you have an immediate suggestion. I’ll let you know if I have success getting it to work in the browser. In any case this is a great library.

      • If you want a solution that runs on both sides, take a look at streamline.js (https://github.com/Sage/streamlinejs). You’ll get the same code structure as with galaxy with a slightly different syntax (more compact) and you’ll have 3 options to run it: pure callbacks, fibers and generators (galaxy being the runtime library in this case). The fibers mode only works server-side; the generators mode can run either side but requires bleeding-edge JS engines; the pure callbacks mode runs everywhere (it has been used to port vim to the browser: https://github.com/coolwanglu/vim.js/tree/streamlinejs).

        We use streamline.js at Sage to develop our product. Streamline.js is our industrial solution and galaxy is more of an experiment that I do on the side to make sure that we’ll be able to take advantage of generators if they bring better value in the future. Today, our product runs very well on top of galaxy but it is slower than in fibers or callbacks mode; so we’re using a combination of fibers on server-side and callbacks on client-side in our released product.

        Streamline preprocesses code. Although this can be made completely transparent (it’s like developing with CoffeeScript), some people just don’t like the idea.

      • crunch483 says:

        Thanks again Bruno. I was actually aware of streamline.js but I liked the idea of syntax that would be future-compatible with ECMAScript 6 environments without requiring preprocessing of the code; thanks for the additional details though, they’re helpful. I wonder how hard it would be to modify streamline.js to look for a “yield” statement instead of an underscore in one of the arguments when it’s doing its source code transformation. Of course that would lose some flexibility, i.e. you’d have to create wrappers for functions that expected the callback as the 2nd or 3rd argument rather than the first, but it seems to me that otherwise this would just be a different syntax that would end up with the same compiled streamline.js code, that would have the added benefit of working natively in the future. I think the only other modification needed would be for streamline to strip out the * from the generator functions when generating the final code. I was almost able to achieve a similar result with the library I mentioned earlier (http://stackoverflow.com/a/11315328/560114) except for the error handling issue, which it looks like streamline has solved nicely. Anyway, while this is an idea I’d like to pursue, it’s nice to know I can use streamline in the meantime :)

      • Yes, it would be possible to adapt streamline to look for yield and function* instead of the _ marker but then there is a risk of mis-interpreting code that uses generators for other purposes (synchronous iterators for example).

        But still, it is not obvious that ES6 generators will be THE mechanism to support async JS programming in the future. Performance matters and today, with our code patterns, we are getting much better performance with fibers than with generators. So I’m not ready to bet the future on generators yet.

        It’s probably in part because generators are not yet optimized but there is also a fundamental issue that goes against generators: generators are restricted to shallow (single frame) continuations while fibers provide deep continuations. Deep continuations avoid lots of allocations and extra calls when dealing with deeps stacks of async calls. So I’m not sure that generators will close the gap on performance and I like the option of being able to switch between 3 modes.

        Ideally, JS should provide a special syntax for async calls. It should have single frame semantics on the surface but be implementable with deep continuations. So I’m still hoping for some better solution in an ES-N (N>6).

      • crunch483 says:

        P.S. Unrelated to my above comment…I just discovered this library, which looks interesting: https://github.com/luciotato/waitfor-es6. Just as another alternative to consider in addition to Galaxy and suspend. I prefer the simple ‘yield’ to ‘yield wait.for’ but it looks like wait.for doesn’t require any wrappers which is nice.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s