Okay, so how does it happen ?
Sometimes in order to understand how something works, the best way is to build it yourself. So let’s flip the question:
Imagine we are given a piece of code that uses
asyncfunctions, how can we rewrite it using only
async function :
It performs three asynchronous tasks, one after the other where each task depends on the completion of the previous task. Finally, it returns the result of the last task.
How can we write it using generators ?
Generators are functions which can be exited and later re-entered. Let’s quickly recap how they work. Here’s a simple generator function :
gen has some interesting aspects (lifted from the MDN docs) :
- When a generator function is called, its body is not executed right away. Instead it returns an iterator-object which adheres to the iterator protocol i.e. it has a
- The only way to execute the body of
genis by calling the
nextmethod on its iterator-object. Every time the
nextmethod is called, its body is executed until the next
yieldexpression. The value of this expression is returned from the iterator.
nextmethod also accepts an argument. Calling it with an argument replaces the current
yieldexpression with the argument and resumes the execution till the next
To elucidate (very, very crudely) ..
- A generator-function gets executed
yield-by-yield(i.e. one yield-expression at a time), by its iterator (the
yieldhas a give → halt → take behaviour, so to say.
- It gives out the value of the current yield-expression, to the iterator.
- It then halts at this point, until the iterator’s
nextmethod is called again.
- When the
nextmethod is called again, it takes the argument from it and replaces the currently halted yield-expression with it. It then moves to the next
You may want to read the above summary again or refer to the amazing MDN docs!
But how does this help us ?
By now you would be wondering, how do the generator functions help our situation? We need to model an asynchronous flow where we have to wait for certain tasks to finish before proceeding ahead. But so far in our discussion everything has been synchronous. How can we do that?
Well, the most important insight here is that the generator-functions can yield
generator function can
promise (for example an async task), and its iterator can be controlled to halt for this
promise to resolve (or reject), and then proceed with the resolved (or rejected) value. This pattern of weaving a an iterator with yielded
promises allows us to model our requirement like this :
(Notice how this generator function resembles our
But this is only half the story. Now we need a way to execute its body. We need a function that can control the iterator of this
generator function to halt every time a
promise is yielded and proceeds once it resolves (or rejects). It sounds complicated, but is very simple to implement, as shown below :
Now we can execute our
init using this
runner function as shown below:
And that’s it! This combination of a
runner function and our
generator function achieves a similar outcome as the original
Please note that this
runner function is only for demonstrating the concept. It is not suitable for any serious use. If you are looking for a proper implementation, you can find it here.