Home ES6 generators and async/await in Typescript
Post
Cancel

ES6 generators and async/await in Typescript

Some exciting features Javascript has developed during the latest versions (ES6, ES2016) are without doubt the generator functions and the async/await support.

These features exist in Typescript for quite some time; the language supported async/await since 1.7 version. But what about them? What they really are and how they work?
In this post I am going to explore their world, their Typescript syntax and implementations.

What is a generator

A generator is a special type of function that during its execution it can be paused, yield results back the caller and resume later, at caller's convenience. And this can happen as long as the generator has something to return, some value to yield back.

This is a very powerful feature, as you can control the flow, send messages back and forth; the generator sends the caller a message every time it yields and the caller passes a message for the generator, when it is restarting it. As we can see later, this kind of behavior is ideal for state machine like style of code, which in this case is the async/await style.

How it works

First, I will talk a little bit about ES6 and how it works there and then I will go to Typescript, but the syntax does not differ.

So, in order to create a generator you use an asterisk after the function keyword and then you casually declare a function, just like this:

Congratulations, you have made a generator!

Of course, there is more on this. In order for the generator to be useful, you need to use the yield expression, to delegate result and control to the caller object.
Note that when you call a generator, you casually call a function, but it's body will not execute, not until you request the generator iterator to iterate to next item.

Say what???

I may have gone a bit ahead of myself here. Let me explain.

Yield expression

With this expression you can send value back to the caller. You use the yield keyword. This creates a lazy iterator. The following is a very simple generator

The yield expression is the code on line 2, the yield "Yo!". You can yield whatever you want, you might want to yield a number, an object, callback or a promise, it is up to you. Generator's job is to return this to the caller once it reaches to that point in the function body.

Generator iterator

Function myGenerator is pretty useless if I don't execute it. Calling this method, I will create a generator iterator. Under the hood, generators support iterators in order to move to the next value. So, the following example, will create that generator iterator object and will iterate that. If I did it right, I will see the "Yo!" value.

Check the following plunk to interact with the code a bit.

On line 5, I create the iterator. Nothing happens.
When I call the .next() method, generator will execute, reach the yield, which in turn sends an object to the caller. Generators yield objects with a value and a done property.

  • value property: Contains whatever value you pass back with the yield expression
  • done property: A boolean that indicates if iterator should finish execution or not.

In the example above, you see, when I fist call .next(), I get { value: "Yo!", done: false }, which, okay has the value, but why generator hasn't finished?
There is no other code to execute after the yield expression.
Yes there is not, but generator is not aware at the moment, you need to resume his execution in order to let him know that his job is done.
When it reaches beyond the last yield, it returns an undefined value and the done property is true.

Check the next example and notice three things that happen during the runtime:

See what I see?

  1. The function body does not execute not until I call .next()
  2. The function pauses its loop that is running to return the result. In the meantime I can do whatever I want and call it whenever I want. Generator will patiently wait for me to delegate control back to it
  3. When I call the .next() method, only and only then the function resumes (the loop in this example)

Of course, it exits when there is no yield expression to execute. Technically, I does not exit, the iterator is just finished iterating through.

Dancing back and forth

I mentioned earlier that generators allow you to do that dance, from the generator function to the caller and from the caller back to the function. You just need to pass a value to the .next() method and it will evaluate from the yield expression.

In the next example, I create a generator that accepts a string input, yields it and when it resumes it console.log's the concatenated values (the initial when generator iterator was created and the one that is passed from the .next() method)

It's a bit weird. What is going on? Shouldn't I pass the value in the first time I call the .next() method?
Nope. When you call the .next() for first time it reaches till yield. There, it returns the hello variable. Only that. I hasn't yet returned a value to the world variable! The external system correctly sees the "Hello" value being returned in first iteration, this is yielded by the generator.

In second iteration, I pass the "World" string. This is what is assigned to the world variable, by evaluating the yield hello expression! Code executes until the console.log statement, where I concatenate the hello and world variables. From that point on, the generator knows that this is the end and returns an undefined value, indicating the iteration is over.

Check this one to understand when the yield expression is returning a value and when it is evaluated

The generator function receives a parameter.
On first iteration, the yield value executes, the one in the parenthesis and returns to the external system. No concatenation has happened yet.
In second iteration, I pass the string "George". The (yield value) expression returns that string in the function, which is then concatenated with the rest, resulting in the printed value.

Generators and promises

In this example I am going to demonstrate that two-way communication between the generator and the external system.
In the following code, I create a function which whenever is called, it returns a promise. That promise generates a random number between 0 and 20 each time and it resolves if it is greater than ten. If less or equal to ten it rejects the promise.

Then I created a wrapper for the generator that I am going to write. That wrapper accepts a generator function as an argument, it creates a generator iterator and calls .next() to fetch generator's value, which happens to be a promise. I use the .then() and .catch() methods to send a value or an error object back to the generator.

Finally, I create the generator function itself, which yields the promise I made earlier. If it is resolved, it takes its value and copies it to an element's innerHTML. If an error object is returned (from the wrapper when the promise is rejected), then the catch body will execute, printing the error's message.

The makeRandom() method is just a helper. I also registered an event listener to a button to repeat the process.

And here is a plunk to play with the source code.

As you can see, this one here, has a lot of similarities with async/await. Imagine, instead of yield was the await keyword. The code would look the same, wouldn't it? More on this in next sections.

Typescript. What does it generate?

From this:

It generates this:

Nice, let's get a little bit ahead and compare this with the code that is generated for the async/await

From this:

It generates this:

Well, comparing the output, I can see similarities. Both produce a generator. So, we actually see that async/await is based on generators for its functionality. Let's explore the reason in the next section.

Async/Await

Let's take a step back and think for a moment. When you write an async function, with an awaiter, what is yourexpectation?

You expect it to pause the execution when the execution reaches the await keyword in front of a method. The execution is back to the caller once the method that is awaited is finished.
You also expect in some cases to pass a value to the method you await.
Lastly, you might expect it to throw an error.

Based on our analysis in this post, these three attributes are supported by generators. Thus, they are perfect for implementing the async/await feature.
Also, if you look back on the section Generators and promises, you will see that this code resembles an async/await pattern a lot. What I did there was to pause the execution and pass control (the promise) to the caller and the caller tried to resolve the promise, pushing a value back to the generator (from the .then() or .catch() methods).
The generator could also handle exceptions (rejections on the promise) gracefully.

Syntax

You prefix the function with the async keyword and inside the function body you prefix with the await keyword the promise method you are about to call.
If you mark a function as async, it always returns a Promise. So, if you do not return something from the function body, your function is of type Promise<void>.

Let's look at some more examples. I am going to demonstrate usage of async functions that do not return a value (return a Promise<void>), functions that return some kind of value and functions that fail, because of Promise rejection.

Void functions

The following function is printing into the console each name in the array, it does not return anything though.

Another example. The following function is updating an HTML element with a greeting, if it is morning, it prints "Good morning", for afternoon it prints "Good afternoon" and for evening "Good evening" along with the current time and day. The setTimeout is there to simulate a delay.

Notice in all the examples, I wrap the Promise in a function, which returns it.

Returning a value

Next example. In this one, I am going to return an object, so I need to resolve the promise that is returned by the async function.
The object I am returning is of type Person, which I created, in line 1, just a simple object with a string and number properties. I return this from the Promise, passing the object into the resolve method. Again, setTimeout is simulating a delay. After I fetch the return value, I create some appropriate messages to show on the UI.

Please note that I am using .then()/.catch() only because I am not in an async function, if I were, I could just await that .fetchPerson() method, like this

Rejecting the promise

In this last example, I am going to reject a Promise and show the error message on screen. When you are rejecting a Promise, the await will throw an error, so it's a good practice to wrap those await calls around a try/catch block. Also, the value that is passed into the reject function of the Promise, is going to be the error object in the catch block.

If you wish to fiddle around with those, I have a plunk ready, give it a go, get familiar with the syntax.

Summary

Async/await is build on generators. Generators make us able to lazy execute code, enabling a two-way communication between an external system and the generator function itself.
Generators in Typescript have same syntax as in ES6, so async/await, but Typescript provides us with strongly typed methods, so we can use the API at its best.
A generator's body is not called, not until the .next() method from the generator iterator is called. This executes the yield expression inside the generator, until there is no yield to call, in this case terminating the iterator.
A generator sends a message/value to the caller and it can also receive input from the caller, through the .next() method, which is evaluated as a return value from the yield expression.

Async/await is available in Typescript from 1.7 version and onward, but only on ES6 targets. From 2.1 though it is available on ES3 and ES5 targets as well.
In order to use async/await, prefix your function with the keyword async first and you can call a promise inside its body, by prefixing it with the await keyword.
An async function always returns a Promise.
Make sure you wrap the Promise object around a function.

This post is licensed under CC BY 4.0 by the author.

Socket.io integration tests with chai and mocha

Testing AngularJS UI-Router routes

Comments powered by Disqus.