DEV Community 👩‍💻👨‍💻

Cover image for Asynchronous JavaScript Explained.
shrikitamir
shrikitamir

Posted on • Updated on

Asynchronous JavaScript Explained.

Asynchronicity in JavaScript.

It's the most used concept in web development.
Understanding it is a huge advantage in interviews.

Knowing how it works under the hood helps us to debug so much easily and it helps us to predict the result in any scenario.
And I know its hard to believe, but it's actually predictable.

But how much do we know on how it works under the hood?
So let's start from the beginning.

Why do we need this asynchronous concept?

JavaScript is a single threaded language which means it can do one thing at a time.
But we don't want our app to be stuck while doing long tasks like waiting for http response.

Use case scenario.

You have in your app a weather forecast and you're making http request to an Api to get the forecast data.
Without using the asynchronous concept, the app will be stuck while waiting for the http response because, well JavaScript is a single threaded language.

So how it works and why?

setTimeout(() => {
console.log('foo')
},1000)

console.log('bar')
Enter fullscreen mode Exit fullscreen mode

Here I'm handling the asynchronicity with a callback.

Which means that when the thread of execution meets the 'setTimeout' line it starts a timer of a second (1000ms) and after the timer is finished the callback function which logs to the console 'foo' will be executed.

If getting a bit more into details setTimeout is actually part of the browser Api but that's out of our tutorial scope (I strongly encourage you to look it up.)

So we can tell pretty confidently that we will see in the console

'bar'
'foo'
Enter fullscreen mode Exit fullscreen mode

And that is true.

But let's try to make the situation a bit more challenging.

setTimeout(() => {
console.log('foo')
},0)

console.log('bar')
Enter fullscreen mode Exit fullscreen mode

What about now? the timer is set to 0.
Then we must see in the console first 'foo' then 'bar' right?

Well, no.

Just like before, we will see

'bar'
'foo'
Enter fullscreen mode Exit fullscreen mode

Let's explain.

I'm assuming you know what the Call Stack is and if you don't know please catch up before continuing because this knowledge is needed for further explanations.

So when using callbacks there's this thing called Callback Queue.
When the timer ended after 0ms, basically immediately, the function which logs 'foo' doesn't just jumps into the Call Stack.
It goes into the Callback Queue.

Only when the Call Stack is empty and finished all of it's sync tasks the function which logs 'foo' is transferred from the Callback Queue on to the Call Stack and getting executed.
That's why we will see 'bar' and then 'foo'.
The callback has to wait to the sync code to finish executing.

Which means, that even if we had a million console.log('bar') after the 'setTimeout'

setTimeout(() => {
console.log('foo')
},0)

for (let i = 0; i< 1000000; i++) {
console.log('bar')
}
Enter fullscreen mode Exit fullscreen mode

We would see a million time 'bar' and then 'foo'.

For a callback to be inserted into the Call Stack all of the sync code must be finished.

The callback is transferred from the Callback Queue to the Call Stack by the Event Loop - another concept which I strongly encourage you to look up.

What about Promises?

const weatherData = fetch('weatherUrl')
weatherData.then((data) => {
console.log(data)
})

console.log('bar')
Enter fullscreen mode Exit fullscreen mode

So in this scenario we're handling the asynchronicity with promises.

A function which logs the data from the Api will be executed once the promise is fulfilled.
Let's say that the data is the string 'hello from Api'.

We will see in the console

'bar'
'hello from Api'
Enter fullscreen mode Exit fullscreen mode

Even if the promise is fulfilled immediately.
So, it's pretty similar to the callback and the Callback Queue.

When using promises there's another thing called Microtask Queue.
When the promise is fulfilled, even if it takes 0ms, the function which logs the data doesn't just jumps into the Call Stack.
It goes into the Microtask Queue.

Only when the Call Stack is empty, the function which logs the data is transferred from the Microtask Queue on to the Call Stack and being executed.
That's why we will see 'bar' and then 'hello from Api'.
The function which logs the data has to wait to the sync code to finish executing.

Just like with the Callback Queue.
The function get transferred from the Microtask Queue to the Call Stack by the Event Loop.

Lets combine both ways handling asynchronicity.

setTimeout(() => {
console.log('foo')
},0)


const weatherData = fetch('weatherUrl');
weatherData.then((data) => {
console.log(data)
})

console.log('bar')
Enter fullscreen mode Exit fullscreen mode

Now, of course the http request we sent takes time so we will see the 'hello from Api' last.
But for teaching purposes lets assume the promise is fulfilled immediately,
we're going to see in the console

'bar'
'hello from Api'
'foo'
Enter fullscreen mode Exit fullscreen mode

If you understood the last explanation actually this explanation will be pretty simple.
So the common sense says that 'foo' needs to be before 'hello from Api'.
We initialized the timer before the http request.
So why it's not the case?

Well the Microtask Queue has priority over the Callback Queue.
which means that even if there's a function sits in the Callback Queue waiting to be executed before the function in the Microtask Queue (just like the example above)
Still the function from the Microtask Queue is going to be executed first.

Okay.
It's time for the last example to ensure you are comfortable with this subject.

setTimeout(() => {
console.log('foo')
},0)

const weatherData = fetch('weatherUrl');
weatherData.then((data) => {
console.log(data)
})

loopThatTakes500ms() {
…
}
loopThatTakes500ms()

console.log('bar')

Enter fullscreen mode Exit fullscreen mode

In this example let's assume that the promise got fulfilled after 300ms.

We will see in the console

'bar'
'hello from Api'
'foo'
Enter fullscreen mode Exit fullscreen mode

Let's go with the thread of execution and explain step by step.

First the thread of execution meets the setTimeout and he sets the timer for 0ms.
So the timer is over immediately and the function which logs 'foo' to the console gets inserted to the Callback Queue.

It's not executed yet! we have more code to execute.
So it sits in the Callback queue and waits.
Even though the timer is over.

Now the thread of execution meets the line which fetches our Api and now weatherData is a promise.

Next thing that the thread of execution does is that it takes the function that logs the data from the Api and puts it to be executed after our promise is fulfilled.

What about our Callback sitting in the Callback Queue and waiting?
People, it's still waiting.

Now our thread of execution meets the line that declares a function which holds a loop that takes 500ms, and puts it in the memory.

Next, our thread of execution executes the function with the loop that takes 500ms.

In the middle of the loop the the promise is fulfilled because remember we said the promise gets fulfilled after 300ms.
So the function that logs the Api response gets inserted into the Microtask Queue.

Finally the function that holds the loop finished.
The thread of execution goes and meets the line that logs 'bar'.
And now 'bar' will be printed to the console.

Now, people.
after a bit more than 500ms.
Our Call Stack is empty.

Remember what we said about the priority that the Microtask Queue has over the Callback Queue?

So, the function that logs the data from the Api is sitting in the Microtask Queue and now it's being transferred to the Call Stack.
Now 'hello from Api' will be printed to the console.

And now, People after waiting so much time!
The callback that logs to the console 'foo',
The callback which waits in the Callback Queue more than 500ms to be executed!
Is being executed.
Now, 'foo' is being printed to the console.

Guys,
That's the whole model of asynchronicity in JavaScript.

This knowledge will help you to understand what is happening and why, and how you can debug it.
This explanation answers the question how asynchronicity works in JavaScript and it will definitely impress your interviewer.

Thanks for reading. Now you're ready to use JavaScript asynchronicity more wisely and you're ready to handle interview questions about asynchronicity!

Top comments (0)

"I made 10x faster JSON.stringify() functions, even type safe"

☝️ Must read for JS devs