Saturday 27 June 2015

Simulating await with ES6 Generators

When I first read about ES6 generators I wrote a post about its differences with C# "iterators" (I'll say it again, horrible naming choice by Microsoft). At that time I read how people were taking advantage of them to write code resembling C#s async/await magic. It seemed hard and interesting to me to understand at first sight how both things were related, but I had no time to look into it at that time. These days, after glancing through this thorough and brilliant article I decided to try to understand it.

The first point to consider is that as mentioned in the article, generator objects created by a generator function implement 2 different interfaces: data producers (iterators, the Symbol.iterator thing), that's what you are used to in C#, and data consumers. The latter is pretty interesting as it's the feature that makes the await emulation possible.

The fundamental idea is that generator objects combine the producer and consumer role in a "next" method that receives a value and returns a value. You represent it like this in your generator function:

var b = yield a;

The essential part in that line is that it really means this:

return a to the current "next" call;
read b from the next "next" call;
and execute the following lines until the next "yield" is found and you return a new value from there);

between both actions, the generator function gets "suspended".

This idea of having your code inside a sort of function (in the end it's a kind of illusion, that function is bended by the compiler into a generator object where state is maintained so that this suspension/reentrancy can really work) is the basis of how async/await works in C#. So, if you return from your yield a sort of Task object that when completed would call the reentrant function again passing it its return value, you basically get the same. Calling the reentrant funtion means calling the generator.next from an async loop, so your Task objects will need a reference to the async loop function (that you will call from the continuation (nextStep property in my example) that you have added to the Task), so that they can continue the cycle.

The JavaScript kind of equivalent to C# Tasks are Promises, and it's what is used by the different libraries (like co) enabling this sort of async/await programming. For exploratory reasons, in order to fully understand this whole thing, when writing my "async/await enabling" function, I've decided to use a very basic TaskInfo object rather than Promises, I think it makes easier to understand how the whole think works with no need to know anything about promises. Indeed, seems like co also works with a sort of lightweight alternative to promises called thunks

I've put up the code in this gist.

No comments:

Post a Comment