DEV Community

Cover image for A Journey Inside the Asynchronous Nature of JavaScript
Ebraheem Said
Ebraheem Said

Posted on

A Journey Inside the Asynchronous Nature of JavaScript

When I first started writing JavaScript, Everything seemed simple and straightforward, code runs from the top to the bottom, line by line, there was no confusion, until I was introduced to Timers (setTimeout, setInterval) then some weird behavior started happening, I couldn't bring myself to understand it back then.

Welcome to the asynchronous nature of JavaScript.

As we were all told, JavaScript is single-threaded, That is indeed correct, JavaScript by itself cannot execute multiple operations at the same time, you can imagine a fast-food restaurant with only one cashier taking orders, this cashier can only take one order at a time, in JavaScript, This cashier is the Call Stack -The call stack is a Data Structure that executes The Last item entered the Stack (LIFO) Last In First Out- JavaScript only has one Call Stack. This means that if a function entered the Call Stack and this function took too long to execute, maybe fetching some data from an API, Will this freeze the whole application while it awaits for this long running function? Absolutely not but to understand this we would need to dive a little bit deeper.

The Main Characters: Web APIs, The Callback Queue and The Event Loop

JavaScript by itself doesn't have built in tools to handle these type of long running operations Like making network calls (fetch) or setting timers (setTimeout), Then how does it handle it?

When you run JavaScript, Usually it runs in the Browser or Node.js Environment, So these network calls and timers are actually provided by the environment.

  1. Web APIs: When the call stack encounters a timer, it doesn't wait around for it, but it registers this timer in the browser and then Immediately runs the next line, The Web APIs handle the waiting in the background.

  2. The Callback Queue: Once the Web APIs finish handling the task in the background, which was in our example waiting for the timer, it doesn't just run the code inside the timer in the call stack and interrupting whatever code inside it, but it pushes the result inside a Queue called The Callback Queue.

  3. The Event Loop: This is the Hero of JavaScript, it has only job and one job only, it constantly checks the Call Stack, is it empty yet? no, continue executing whatever synchronous code inside the call stack, is it empty now? Yes, then lets push the first function inside the Queue to be executed in the Call Stack.

It is also nice mentioning that there are two kinds of Callback Queues, 1- Macro Task Queue (less priority) for timers,
2- Micro Task Queue (higher priority) for promises,
the diagram below explains the priority difference.

The Evolution of Asynchronous Code in JavaScript

Over the years, software engineers wrote different kind of async code in JavaScript

  1. Callback Functions: Originally we passed functions inside other functions to be executed later but if you had too many functions depending on each other it would evolve into unreadable code known as "Callback Hell"

  2. Promises: Promises gave us a better/cleaner way to handle future values, A promise is exactly how it sounds like, An object that holds a value that will eventually arrive.

  3. Async/Await: The Modern Era, it was introduced in ES2017 and its nothing but a syntactic sugar for promises, it makes us write asynchronous code that looks synchronous, you first declare the function with async keyword and each step or "checkpoint" that Pauses the function we write "await" before it then it yields back the control to main thread to execute other code making it completely Non-Blocking.

We understood that JavaScript is indeed single threaded but the Event loop and the Environment that JavaScript runs in, gives JavaScript those superpowers that lets it run Asynchronous code, we understood the call stack first runs the synchronous code, we also knew that Event loop constantly checks for The Call Stack, is it empty? to push whatever inside the Queues, we also knew that there are two kinds of Queues, one for timers (Macro Task Queue), another for promises (Micro Task Queue), then we took a look at the evolution of Async code in JavaScript.

This journey has come to an end, but there are many more to come.
Thank You for your time!

Top comments (0)