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.

21 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]".

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