Implement the Async/Await Pattern and be Familiar with the Generator

Babak Gholamzadeh
Frontend Weekly
Published in
11 min readMay 8, 2020

Understanding the main ideas of this pattern by implementing it from scratch (in an evolutionary way)

Photo by Andrea Piacquadio from Pexels

If you came here and want to know more about async/await pattern, you probably have some experiences of using callbacks and had some struggles with callback hell(if you need to review some of the quirks about callbacks, you should check this article out). Also, you might have some experiences of working with promise pattern.

Prerequisites for going further in this article is to have some ideas of how to use promise; but if you don’t, you’d better visit this page on MDN before continuing (and if you are really curious about the promise pattern and intend to fully understand it, you can read the book that I’ve published here for free).

Start from the past

Let’s back to the days that there wasn’t any syntax for using async/await pattern to make the workflow of our codes clean and beautiful.

In those days, after adding promise natively in ES6 (aka ECMAScript 2015) and hanging around with this new pattern, we found that this pattern is really useful. It can solve many of the problems and struggles that we had with the ugly callback pattern and also makes our codes more readable.

But as we all agree, nothing is perfect and everything can improve

When we had started using promise instead of callbacks wherever possible, we saw that we still have to repeat the then method to run some sequences. There is still some boilerplate that is cumbersome to use.

On the other hand, it was kind of pain that we couldn’t access the variables in the previous then method scope unless we defined them globally (which is not a good idea).

And some of these sorts of excuses had bothered us.

Make a long store short, finally, we realized that we need something that makes our asynchronous workflow to look more synchronous. Because our mind is used to understand that structure better and feels more comfortable with that.

So to meet these needs, we started to analyze some ideas that came to our mind, and all of a sudden, we noticed that another cool and magical feature was added to JavaScript in ES6 as well.

And that was the generator function.

The original photo by Hello I’m Nik 🎞 on Unsplash

Let me introduce you to this unicorn

The generator function has a weird and amazing structure somehow. It is the only function in JavaScript that its execution can be paused at a point and then be resumed again (actually I fell in love with it).

Note: Here we are not going to deep dive into the generator function. We just want to get familiar with it to see some of its practical benefits that can help us to implement the async/await pattern.

Let’s see its applications in an example:

As you can see, there are only two things in its syntax that we must mention:

  1. The asterisk (*) before the name of the function, defines that this function is a generator function. The asterisk just must be put between the function keyword and the name of the function (which is gen here). So all of these states are correct: function* gen, function *gen, function * gen, function*gen.
  2. yield keyword that is the point that function will be paused there.

The behavior of the generator function is different from other functions (make yourself ready to see some magic).

When you invoke this function by using gen(), the codes inside the function wouldn’t be executed immediately. By invoking that, it just returns an object (which is called a generator object) that has some methods on it. One of its most important methods is the next method.

At this point, by calling this next method we can run the gen function until we reach the first yield keyword and it would be paused there automatically.

When the execution be paused at that point, the value in front of the yield would be returned and it’s accessible by the next method.

As you can see, the returned value of the next method is an object which has simply two properties.

  1. Value: It is the value in the front of yield that be returned on each pause
  2. Done: It shows whether the execution of the gen function is done

After you call the next method a few more times, you will get the below result:

After the third time that the next method be invoked — because there isn’t any yield keyword anymore — the execution of the gen function be done (which you can see in the done property) and also the value in the value property becomes undefined.

Actually, the last value is fed by the return statement, and because we don’t have any return keyword here, it is undefined (if a function doesn’t have any return keyword, JavaScript returns an undefined value from that function by default).

So far, we learned that a generator function can be paused at the position of a yield keyword and also returns the value that is in the front of the yield on each pause.

Another amazing thing that we can do is to pass an argument on each pause.

There is a point here that is important to keep in your mind; the argument of the first next method will be thrown away and by invoking that you are just able to get the returned value of the yield statement (I know it may sound weird at first, but as thinking about it more, it will make sense eventually).

But by invoking the second next method you can pass an argument to the first yield and execute the rest of the code until reaching the next yield statement (or a return keyword, or the end of the function).

As you can see here we used a return statement in the gen function, then we got the returned value on calling the last next method.

The only two other things and we might need to know about the generator for this topic, are the return method and the throw method on the generator object.

Whenever these both methods be called — that doesn’t matter at each pause — the execution of the generator function would be stopped immediately no matter what.

The only difference between them is that when we use the return method, we just end the function normally and get an object like this { value: undefined, done: true } (the value property equals to the argument of the return method). But when we call the throw method, we end the function by throwing an exception.

Let’s back to asynchronous stuff

Photo by Startup Stock Photos from Pexels

So far we have learned about some of the magical applications of the generator function. What if we use the generator for controlling the workflow of our asynchronous functions?

The idea is that we can use the generator to run the async functions and then pause until that function be resolved. The important thing about pausing with the generator is that it doesn’t block the program. Then it can be resumed again by the async function itself.

Let’s see the idea in the code to understand it better:

Until here, we called an asynchronous function in the front of the yield that returns a promise object. It means the returned value of calling the next method would be a promise object (unlike the previous examples that we returned just a simple value).

After this point, we can call the then method on the promiseObject because it’s just a normal promise that we used already.

The key point here is that we called the next method inside the then method (which means after resolving the async function) and pass the resolved data to it as an argument.

This data will go back to the gen function and will be put inside the fetchedResult variable. Also the execution of the gen function will be resumed.

This code is kind of ugly, I know, but if you just take a closer look at the gen function, you can notice that we call an asynchronous function in a way that the workflow looks like synchronously somehow (of course it works asynchronously but the appearance of it is similar to synchronous codes).

I guess we got the idea at this point (if you didn’t, just try to play with the code or continue reading the article to eventually grasp it).

Before going further, let’s refactor this code to be more comfortable for adding features and some improvements.

This is exactly the previous code that we simply wrapped all the operations inside a function named asyncController and passed the generator function to it as an argument.

Add features and improvements

The first thing that we notice which needs improvement is that our asyncController function now can handle just one single async function and one pause. How about running a bunch of asynchronous functions one after each other?

We need some improvements in our asyncController to be able to handle a situation like below:

All the operations (i.e. fetchData, fetchedResult.json, saveData etc.) are asynchronous and we want them to be run respectively.

In order for the asyncController to handle this kind of situation, it just needs to repeat its operation until the last yield statement be resumed and the generator function is done.

But for implementing this iteration we cannot just use a simple loop (i.e. for(){}, while(){}, do{}while() etc.), because these sort of iterations are unpausable. We need a solution that we could control the flow of the iteration.

A way that comes to our mind is to use another function to run the operation and call it again whenever we want to repeat the operation for another yield statement.

Except for the first line that we just get the generator object from the generator function, we put the rest of the codes inside another function named iterator, and call it for the first time.

Look inside the then method of the promiseObject — I just commented the previous code for you to see the change — we didn’t call the next method here, rather we called the iterator function recursively and send the data to it as an argument. The argument will be passed to the next method on the next round and this pattern will be repeated.

But wait! We forgot something here. How long is it going to iterate? Where is the base case of this recursive function?

We remember that the returned value of the next method is always an object which has two properties and one of them is the done property that tells us whether the generator function has been done.

Here we can use done property for the base case to terminate the iterator function.

OK! This improvement is done! Let’s go to the next one.

The next thing that we ought to mention is that we all know whenever we use promise, it is wise to always add a catch method at the end of the chain.

So what do we suppose to do with the error that we catch here?

As we said before, the generator objects have another method named throw that is the best time to use it. We can throw the error to that method which would be handled inside the generator function.

You can call the throw method inside the catch method but in my opinion it’s better not to do that and call the iterator function again and then send the error to it. Because I think we should put all operations related to the generator object in one place (if you don’t like this approach, you can do that in your way. There might be plenty of ways to do that).

But if we send the data as well as the error in the same argument, how can the generator object recognize to run which method of itself?

Because for the errors it must call its throw method and then call its next method for the correct data.

Also, there are many ways to do that which is up to you to decide. Here we choose to send the name of the method that must be called, as another argument (which may not be the best way).

Look at the code carefully. As you can see we pass the name of the method alongside the value (which can be the correct data or an error). Also we need to pass the next method name for the first time that we called the iterator function.

So now we pass the error to the generator function. But how is it going to be handled there?

Because the generator function runs synchronously, the errors that might occur there can be handled in a synchronous approach as well. Yep, I’m talking about try/catch blocks.

Oh! Look at this. It looks beautiful, doesn’t it?

But we’re not done yet, there are still some things we need to watch out. Let’s back to work.

By our implementation so far, the asyncController only enables us to handle async functions. But how about handling a simple value? What if someone put a string value in front of a yield statement?

Because we assume that everything is put in the front of a yield should return a promise object, it’s a good idea that we wrap any kind of value inside a promise before resolving.

By using the resolve static function on the Promise constructor, we can wrap any kind of value inside a promise object.

The next important thing which must be considered is that it is expected that our asyncController function should return a new promise object.

It makes our controller handier to be used with other workflows and the result of that be handled by another function. So returning a new promise object is a wise choice.

In order to return a promise object, you can use the resolve static function that we used before, or simply use the new keyword before the Promise constructor to create a new object and return that.

Let’s use the new keyword this time.

First let’s wrap everything inside the callback of the Promise constructor.

Then the resolve and the reject functions should be used somewhere.

The resolve function must be called here because this line is the last thing that will be run if everything goes well.

And the reject function must be called whenever that something goes wrong inside the generator function.

The only place that we can catch its error is when we call the methods on the generator object.

Let’s put it inside a try/catch and call the reject function in the catch block.

By returning a promise object, you are also able to add a then and a catch method after the asyncController function, or maybe use that inside another generator function which is wrapped by an asyncController.

We are done. This is kind of the implementation of the async/await pattern, that we just don’t have its syntax.

Note: you can get the source code from here which is also a much cleaner version of it.

Conclusion

In this article we just focused on the understanding and implementation of the main concepts of the async/await pattern. definitely, this is not the exact implementation that is used to implement this pattern natively.

Here we intended to just talk about the ideas and concepts of this pattern and also get you familiar with the generator functions.

Now that you saw the underneath of these structures, maybe some thoughts might fly to your mind that could change everything and build some new patterns and structures which be used for some special cases.

Also fully understanding this kind of stuff can help you to understand the more complicated patterns.

As a programmer, do not distract your mind by some fancy names and tools. Your mission is to swim deep in the oceans of the programming concepts by your mind.

--

--