Implementing Async/Await
Tags: javascript, typescript, node, webdev
Published: 2021-02-15 19:33:30

On my last [post](https://dev.to/gsarciotto/generators-in-typescript-4b37) we saw the theory behind generators in JS/TS. In this article I will apply those concepts and show how we can use generators to build something similar to **async/await**. In fact, *async/await* is implemented using generators and promises.

# Delving into async with callbacks

First we will show how we can deal with asynchronicity using generators by writing an example with callbacks.

The idea is as follows. When using callbacks, we pass some function that will be called whenever the async action has finished. So what if we don't call a callback, but instead call `next` on some generator? Better yet, what if this generator is the code that called our async function? That way we would have a code that calls some asynchronous process, stays paused while the asynchronous process isn't finished and return its execution whenever it is ready. Check this out:

![Implementation with callbacks](https://dev-to-uploads.s3.amazonaws.com/i/dvldwnp4jizbycl8ybxw.png)

If you don't know what is `...args` in the implementation above, take a look at [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax).
We wrap our asynchronous operations with `asyncWrapper`. This wrapper just pass a callback to give control back to the generator `main` whenever the async process is completed. Notice how our code in main looks totally synchronous. In fact, just looking at `main`, we can't assert if there is anything asynchronous at all, although the `yield` gives a hint. Also notice how our code is very similar to what it would have been with `async/await`, even though we don't use `Promises`. This is because we are abstracting away the asynchronous portions from our consuming code `main`.

Using callbacks like above is fine, but there are some problems.
1. The code feels weird. Why should `main` know about `asyncWrapper`? `main` should be able to just call the async operation and everything should be handled in the background.
2. Where would we do error handling? 
3. What if the asynchronous operations calls the callback multiple times? 
4. What if we wanted to run multiple async operations in parallel? Since a yield corresponds to a pause in execution, we would need to add some complicated code to decide if when we call `next` is it to execute another operation or is it because an asynchronous operation has finished?
5. We have the problems that normal callbacks do (callback hell, etc).

# Promises to the rescue

We can solve the problems above utilizing [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). We will begin with a simple implementation with only one yield and no error handling and then expand it.

First we need to make our asynchronous operation `addAsync` return a promise, we will deal with the case that it doesn't later.

![addAsync with Promises](https://dev-to-uploads.s3.amazonaws.com/i/jpn26dpnhypfgbo30wkl.png)

To solve 1, we need to change our *wrapper* to receive the code that we want to execute, becoming a *runner*. This way our *runner* does the things it needs and gives control back to our code whenever it is ready, while hiding how anything works from our code. The *runner* needs to do essentially two things:

1. Initialize our code.
2. Take the promise that is yielded to it, wait for its fulfillment and then give control back to our code with the resolved value.

![Basic runner implementation](https://dev-to-uploads.s3.amazonaws.com/i/zr3itox2mup29o7a3e87.png)

And that's it! The problem 3 from our list is automatically solved whenever we use promises. The full code is the following:

![Basic implementation with promises](https://dev-to-uploads.s3.amazonaws.com/i/xchigo3qouuhfu038vb2.png)

Let's walk through the execution.

1. First we call our *runner* with the `main` function generator.
2. The *runner* initializes our generator and then calls `it.next()`. This gives control to `main`.
3. Main executes until the `yield`. It yields the return value of `addAsync`, which is a promise. This promise is unfulfilled at the moment.
4. Now the control is with the *runner*. It unwraps the value from the generator yield and gets the promise. It adds a `.then` that will pass the value of the fulfilled promise to `main`.
5. Whenever the promised is resolved and the *runner* gives control to `main`, the yield expression evaluates to the resolved value of the promise (5) and continues the execution until the end.

#### Dealing with non-Promises values

At the moment, our *runner* expects to receive a *Promise*. However, by the spec, you can await any value, *Promise* or not. Fortunately, to solve this is very easy.

Consider the following synchronous add function:

![Synchronous add function](https://dev-to-uploads.s3.amazonaws.com/i/yklr70v9bs1cbwzsit77.png)

This code crashes our generator, since our generator tries to call a `.then` to the yielded value. We can solve this by using `Promise.resolve`. `Promise.resolve(arg)` copies arg if it is a *Promise*, otherwise it wraps arg in a *Promise*. So our *runner* becomes:

![Runner is now able to deal with non-Promise values](https://dev-to-uploads.s3.amazonaws.com/i/6gkrbw7glebyvl4sqmls.png)

Now our code doesn't crash with non-Promise values:

![Execution with synchronous function](https://dev-to-uploads.s3.amazonaws.com/i/lir8wuy71zma0yqc3go8.png)

If we run our code with `addAsync`, we will get the same behavior as before!

#### Dealing with errors

Since we are using *Promises*, we can easily get any error/rejection that happens in our asynchronous operations. Whenever a promise rejection occurs, our runner should simply unwrap the rejection reason and give it to the generator to allow for handling. We can do this with the `.throw` method:

![Error handling example](https://dev-to-uploads.s3.amazonaws.com/i/dg41bq5wnqt6h3u7e9ri.png)

Now not only we add a `.then`, but also a `.catch` to the yielded *Promise* and if a rejection occurs, we throw the reason to `main`. Notice that this also handles the case where we are performing a synchronous operation and there is a normal `throw`. Since our runner sits below `main` in the execution stack, this error will first bubble to the `yield` in `main` and be handled there in the `try...catch`. If there was no `try...catch`, then it would have bubbled up to the *runner* and since our *runner* doesn't have any `try...catch` it would bubble up again, the same as in *async/await*.

#### Dealing with multiple yields

We've come a long way. Right now our code is able to deal with one `yield`. Our code is already able to run multiple parallel asynchronous operations because we are using *Promises*, therefore `Promise.all` and other methods comes for free. Our runner, however isn't able to run multiple `yield` statements. Take the following generator:

![Generator with multiple yields](https://dev-to-uploads.s3.amazonaws.com/i/aepgdtujmv9p3cynmhkh.png)

Our *runner* will deal with the first `yield` just fine, however it won't correctly give control back to `main` at all in the second `yield`, the timeout will finish and nothing will happen. We need to add some iteration capability to the *runner* so that we can correctly process multiple `yield` statements. Look at the following code:

![Multi-yield code](https://dev-to-uploads.s3.amazonaws.com/i/c1pb6jjknq026esvedt9.png)

We use recursion with an [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) to iterate through the generator. Instead of directly calling `.next`, we recursively call this *IIFE* with the unwrapped value of the promise. The first thing the function does is to give control back to the generator with the unwrapped value. The cycle then repeats if there is another `yield`. Notice that on the last `yield` (or if there isn't any), then the generator will end and give control back to the runner. The runner checks if the generator has ended and finishes execution if positive.

There is one problem however: if one of the promises rejects, then the cycle is broken and our runner doesn't run correctly. To fix this we need to add an error flag and call `.next` or `.throw` based on this flag:

![Multi-yield runner with error handling](https://dev-to-uploads.s3.amazonaws.com/i/09vxzmm2g31t09dsid92.png)

# Conclusion

We've implemented something really close to *async/await*. If you look at the [V8 blog](https://v8.dev/blog/fast-async) you will notice that our program does essentially the same thing. I suggest reading the blog post above, there is a cool optimization that if you *await* promises, then the engine is so optimized that your code will run faster than just using promises with a `.then`.

With this post I finish writing about generators, at least for now. There is an interesting topic that I didn't touch that is [coroutines](https://en.wikipedia.org/wiki/Coroutine). If you want to read about it, I recommended this [post](https://davidwalsh.name/concurrent-generators).

For my next post I think I will write about [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) or the Myers diff algorithm (the default diff algorithm for git). If you have any doubts, sugestions or anything just comment below! Until next time :)