Skip to content
HackAndStuff

Taming the Async JavaScript Beast

Programming4 min read

Async JavaScript

If you have ever came across terms like Callbacks , Promises , Async await , Web API , Event loop and you are like WTF is this stuff dude , that's what this post is about . Once and for all , Let's just understand all of this.

WHY do we even need it ?

JavaScript , at its core , is a Single threaded language. It has a single call stack . It is synchronous in nature . Now what all these cool terms mean is that Javascript can do one thing at a time . JS is lazy . It can't handle multiple things. Every time you run a .js file , this guy starts from the top of the file , executes one line at a time, until it finishes. That's it . This is what we call synchronous behaviour or more rudely blocking nature of code because as it is executing one piece of code , it is blocked and can't do some other stuff.

Let' see some simple 'blocking' code

1console.log("Start");
2
3function getData() {
4 return { name: "Elliot" };
5}
6
7let data = getData();
8
9console.log(data);
10
11console.log("End");

In console

Start
{ name: 'Elliot' }
End

As expected , right ! Top to bottom , one thing at a time. Cool . Ok, so what's the problem? WHAT IF, we have a piece of code that is slow . Well then , if the code takes 2 sec , you have to wait for 2 sec and then move on . But that's SLOW. People don't want slow. That's where Async code comes or as the cool guys say Non-blocking Code .

Async Coooodeee !

First , let's see what async code reaal simple async code looks like

1console.log("Start");
2
3function getData() {
4 // simulate server delay
5 setTimeout(() => {
6 console.log(`Got data , returning now`);
7 return { name: "Elliot" };
8 }, 2000);
9}
10
11let data = getData(); // data not ready now , so returns undefined , Ahh this is a problem .
12
13console.log(data);
14
15console.log("End");

In console

Start
undefined
End
Got data , returning now

Okayyy! Let's go our way TOP to BOTTOM .

  • logs Start
  • registers the function
  • calls the function

Now , setTimeout is async stuff , so what JS does at this point (being lazy) , it delegates this work (setTimeout) to some other guy ( We will see who he is later ) and then just moves on . Now because the other guy hasn't finished the work , we get undefined . And then it logs End . Cool ! Okay , now we have two questions

  • Who the other guy is ?
  • How do we know when he is gonna finish the work ? (The undefined problem in above code)

Let's answer the first one.

The Environment and its API

JS Big Picture

So , the other guy that does all the work for JS is the environment in which JS is running . Now, most of the time JS runs in browser (Let's just ignore nodeJS for now). So here, the environment is BROWSER. The browser takes the work , uses its Web API's to do the work and returns the result (!Simplified) .

The stuff like setTimeout , fetch , DOM , it doesn't belong to JS. Yeah , that's weird right , we use it all the time while writing JS code . But, yeah this is all environment's property . What happens under the hood is

  • Browser takes up the work
  • Processes it using its Web API (setTimeout in our example)
  • Puts the result in callback Queue

Wait , what ? What's this callback queue now . Why can't we just return the result back to JS . Well , dude , you can't just go and jump in the middle saying Here's your result , it can break stuff ! It's like you juggling with 5 balls and someone just throws another one at you , now you may catch it , but it's dangerous. JS doesn't want that . He likes playing safe . His condition is Give it to me , when I'm free.

So how do we do that ? (Drumroll...)

The EVENT LOOP

The event loop is the simplest guy in the big picture of JS runtime . His only job is

Keep staring at the JS callstack , once it is free , take the first thing from queue , put it in callstack.

Yup , that's it . Now ofcourse there are priorities in callback queue too like macroqueue, microqueue and rendering stuff, but let's save that for later . We wanna focus on event loop here . It just checks continuosly if I can place stuff on callstack and when he can , he just takes it from queue and places it . Simple and cool !

Now that we know who the other guy is , let's go to the second question , how do we know when the job is done. The short answer is we can't . Yeah , sorry but there's no way you can wait synchronously for the job to get done. But ofcourse that doesn't stop us . Let's do some self introspection . Why do we want to know when job is going to finish. Because , we want to do other stuff with the result , and we can do that only if we have the result .

This is where we use the most intuitive and obvious stuff , The callbacks .

Callbacks

Once we have the result , we wanna do stuff with it . So , what we do is , pass all of that stuff we wanna do to the async function and tell him that once you get the result , do this . That's exactly what callbacks are. You put all the processing you want to do with result in a function , pass that function (or as the cool guys say it CALLBACK ) and tell the async stuff( setTimeout in our example ) to run it when it gets the result . Let me show you what I mean.

1console.log("Start");
2
3function give(success_wrapper) {
4 setTimeout(() => {
5 // Whenever you get it , just do something with it , I will give you a function wrapper , OK!
6 success_wrapper({ name: "Elliot" });
7 }, 1000);
8}
9
10let res = give((data) => {
11 console.log(`Do this with it , the data is ${data.name}`);
12});
13
14console.log("End");

In console

Start
End
Do this with it , the data is Elliot

Here the success_wrapper we passed is the callback . Simple, right! Now what if we want to do stuff when we don't get the result back .Yeah , pass a failure wrapper .

1console.log("Start");
2
3function give(success_wrapper, failure_wrapper) {
4 setTimeout(() => {
5 // if success
6 //success_wrapper({ name: "Elliot" });
7 // else
8 failure_wrapper(new Error(`Error 401 , Not authorized man!`));
9 }, 1000);
10}
11
12let res = give(
13 (data) => {
14 console.log(`Do this with it , the data is ${data.name}`);
15 },
16 (err) => {
17 console.log(`Ahh! Something bad ${err.message}`);
18 }
19);
20
21console.log("End");

Okay , this is cool . Now what if we want to do some other stuff with the result , say , once we get the name we want to get his profile . Simple , put a call to getUserProfile in the success_wrapper , right ! Let's see it.

1console.log("Start");
2
3function give(success_wrapper, failure_wrapper) {
4 setTimeout(() => {
5 success_wrapper({ name: "Elliot" });
6 //failure_wrapper(new Error(`Error 401 , Not authorized man!`));
7 }, 1000);
8}
9
10function getUserProfile(name, callback) {
11 // server delay
12 setTimeout(() => {
13 callback(["boy", 21, "cool"]);
14 }, 2000);
15}
16
17let res = give(
18 (data) => {
19 console.log(`Do this with it , the data is ${data.name}`);
20 // Get his profile too
21 getUserProfile(data.name, (data) => {
22 console.log(data);
23 });
24 },
25 (err) => {
26 console.log(`Ahh! Something bad ${err.message}`);
27 }
28);
29
30console.log("End");

This is becoming somewhat weird now to read. We have callback inside callback. As we wanna do more stuff , we will have callbacks inside callbacks ......inside callbacks ... . This is what we call the Callback Hell or more rudely The Pyramid of Doom.

Conclusion

If you made it till here , Congrats ! You now have a working knowledge of what Async code is , how JS guy works under the hood , what is event loop , how JS delegates work and what callbacks are ! Awesome ! In the next post , we will see how we can save ourselves from this ugly callback hell. Till then

Stay Curious and as always

See ya later!

© 2021 by Jatin Malik. All rights reserved.