A way that async/await-style functionality might make it into browsers
Many moons ago (actually it’s 35.7 moons ago) I wrote an excited post about JavaScript generators in Firefox. Sadly they are still only in Firefox, and there’s no sign of them appearing elsewhere.
But one way they could appear elsewhere is via compilers that generate plain JavaScript, and the latest player is TypeScript. Why is this a good fit? Because, with a very thin layer of helper code, generators replicate the functionality of C# 5’s async
/await
, and if that was a good idea for C# and Silverlight, it’s got to be a good idea for TypeScript. (The downside is that the auto-generated JavaScript would not be very readable, but I can still dream that this will happen…)
My old post is somewhat messy because I was unaware of jQuery.Deferred
. If we bring that into the mix, to serve as the JS equivalent of Task, then things get really nice. To wit:
async(function() { // sleeping inside a loop for (var n = 0; n < 10; n++) { $('.counter').text('Counting: ' + n); yield sleep(500); } // asynchronous download $('.downloaded').text(yield $.get('test.txt')); // and some more looping, why not for (var n = 0; n < 10; n++) { $('.counter').text('Counting: ' + (10 - n)); yield sleep(500); } });
In other words, by passing a function to something called async
, I can use the yield
keyword in exactly the same way as C# 5’s await
keyword.
The yielded values are just jquery.Deferred
objects – or rather, they are objects that contain a function called done
, to which a resulting value may be passed (at some later time). So the implementation of sleep
is straightforward:
var sleep = function(ms) { var d = $.Deferred(); setTimeout(function() { d.resolve(); }, ms); return d; };
By calling resolve
, we trigger any registered done
functions. So who is registering? This is what async
looks like:
var async = function(gen) { var result; var step = function() { var yielded; try { yielded = gen.send(result); // run to next yield } catch (x) { if (x instanceof StopIteration) { return; } throw x; } yielded.done(function(newResult) { result = newResult; // what to return from yield step(); }); }; gen = gen(); // start the generator step(); };
So async
calls the function passed to it to get a generator object. It can then repeatedly call send
on that object to pass it values (which will be returned from yield
inside the generator). It assumes that the objects that come back from send (which were passed to yield
inside the generator) will have a done
function, allowing us to register to be notified when an asynchronous operation completes.
Note that async
could use some further complication, because it currently doesn’t deal with exceptions (note that the try
/catch
block above is merely to deal with the strange way that generators indicate when they’ve finished). But generators have full support for communicating exceptions back to the yielding code, so it should all be do-able.
And that’s all there is to it. You can see the example running here:
http://earwicker.com/yieldasync/
… but only in Firefox, of course.
Ruby’s yield
One of the great things in C# is yield return
. It’s a form of continuation implemented atop the CLR in an ingeniously lightweight way, purely by transforming the body of an ordinary method into a state machine class.
Read more…
JavaScript Generators
These work annoyingly well. Why annoying? Because they’re Mozilla specific. If only we could use them all the time. The other thing that’s annoying is how they are actually better than the C# equivalent.
There are two major advantages compared with C#’s yield return.
Read more…