Async/Await in depth
Published: 2020-11-17 14:59:35

Here we are again, continuing our in-depth series, we are going to talk about a powerful programming concept that every programmer should understand well, let's dive in.

This article is about asynchronicity, i know that this subject is wildly covered in blog posts, YouTube tutorials, and many other places, but it is always approached from one angle and rarely when it gives us a view on the big picture of the matter, that's what we are going to do here, so, we are not going to spend much time on what's already available on the internet, but we are going to say a word about it for the completeness.

Let's start with some definitions, what we mean when we say that a program is synchronous is that it runs in a sequential manner, the program executes every instruction in the order in which it is written when the instruction is totally processed it passes to the next one, and so on. In contrast, an asynchronous program, mildly put, doesn't necessarily respect this rule, it just launches the execution of an instruction and immediately passes to the next one without waiting for the processing of the previous instruction to finish.

The main purpose of this concept becomes clear when we talk in the context of single-thread programming languages, the trade of omitting multi-thread management in favor of less complexity comes with some pitfalls, contrary to multi-threaded programming languages which can launch multiple threads to process different tasks like I/O or a time-consuming function and process them in parallel to the main program, a single thread programming language will hang, waiting for a task to finish so it can get back the control of the thread to run the next task, this result a poor performance, and waste of available processing time. 

For that matter, asynchronicity comes to the rescue, as it gives a sense of parallelism to a single thread programming language, the program still runs in a single thread but not in an ordered, sequential manner, it implements some mechanisms that we are going to dig into later, that allow the program to continue its execution while waiting for the results from other tasks.

The first mechanism that implements this concept is CALLBACKS, a function that does something asynchronously should provide a callback argument where you put the code to run after it's complete. I'm not going to spend much time explaining this concept as there are unlimited resources and examples all over the internet, but i think the idea is clear, the problem emerges when a series of functions depend on each other results, we have many nested functions that become a callback hell.

![callback hell](https://miro.medium.com/max/1200/1*sOy11ZsU1ijCSjZwx8ZzGQ.jpeg)

The next mechanism that emerges from this hell called promises, built on callbacks but it allows a much cleaner implementation, as the name suggests, it is a function that promises to return the result whenever it is available, when it finishes the task, it should call one of these callbacks, 'resolve' if the code success, 'reject' if an error occurred, the callbacks are internal to the promise, so you don't have to deal with them directly in the code, that avoids the CB hell, and you just base your code on the promise if it's a success then you execute some task if not you handle the error, besides this it allows a natural order of code, many calls and chaining of CBs hell with the possibility to pass the result value.

![promises](https://miro.medium.com/max/2628/1*TTH0REFmuSEw8VBmcToY8g.png)

Better isn't it?!

Well, the community doesn't stop here, the current implementation of the asynchronicity is much cleaner and straight forward, async/await, a special syntax to work with promises, async before a function means a function always return a promise, other values are wrapped in a resolved promise automatically, await work inside an async function, it makes the program wait until that promise settles.

![async/await](https://miro.medium.com/max/944/1*-JQKo_gKJvErqJBOeI9JcA.png)

Now that we covered the big picture of this subject, you know the context in which this works and the different implementations that exist, we can explore how this stuff works.

In reality, it always boils down to callbacks as they are the main mechanism to implement asynchronicity, so we may consider, both, promises and async/await as syntactic sugar and clever CBs implementations that allows your code to be clean and readable, for instance, the async/await uses Generators to achieve its functionality, a generator is a function that can return multiple values instead of just one final return, it uses yield to return many times one after another and on-demand, the await statement is just yielded statement under the hood to pause execution, that allows the async function to pull out the value of a promise even though it is nested inside a callback function.

From an execution angle, you have to know how single thread languages work, a language like JavaScript work with an event loop that decides which code to execute next, an event is fired due to some trigger, and consecutive functions are fired after that until there is nothing to call.

For that matter, it uses some data structures to keep track of the execution timeline, a call-stack (LIFO), a message queue (FIFO), and a job queue (FIFO), you can imagine the event loop as a continuous job that loops on those data structure to execute waiting tasks (messages).

![event loop](https://miro.medium.com/max/1600/1*iHhUyO4DliDwa6x_cO5E3A.gif)

So when a program start, it goes through lines of code in an asynchronous manner, every function call in the way is added to the call-stack to keep track of the execution, what function is currently being run, and what functions are called from within that function, while events (onClick, onLoad, callbacks) are queued in the message queue, with promises, a special queue is implemented to keep track of their execution (job queue).

You can think of this as follow, a message queue is a macro-tasks queue and call-stack is the micro-tasks queue, the event loop check if there is any macro-task, if yes, it takes the oldest one, it executes the correspondent micro-tasks then it goes back in another round to the macro-task and continues this way until its empty.

I hope this article clarifies some concept for you, thanks.

[![twitter](https://icons.iconarchive.com/icons/limav/flat-gradient-social/16/Twitter-icon.png)](https://twitter.com/clust3r0)