Asynchronous Javascript - Callbacks, Promises, Async-Await | (A detailed overview)

Mohtasim AhmedMohtasim Ahmed
9 min read

First of all, we need to know about Asynchronous Javascript and Synchronous Javascript.

Javascript is a single-threaded programming language. In simple words, JavaScript can do one thing at a time. And javascript runs codes step by step (one after another). So, that means Javascript is synchronous by default. But it has some asynchronous features also. Here I will be writing a comprehensive overview of javascript's both asynchronous and synchronous behavior.

Synchronous Blocking Behavior of Javascript:

Let's go through a real-life example first:

(I urge you to watch this video to have a better understanding)

In the video, you can assume that the waiter is like a thread. So, here the waiter first takes orders from the "customer1" and moves to the kitchen to process the order and stay there (in the kitchen) as long as it takes to process it (blocked). Whenever the order is processed, the waiter then takes the food and serves it to the "customer1". After serving, he is free and moves to his place. Then he takes orders from the "customer2" and goes through the same process.

(Now, while the waiter was processing all the orders of "customer1", "customer2" was waiting the whole time as the thread was blocked in the kitchen) : This is an example of synchronous blocking behavior.

Now, Write the code in a code editor and run the following:

Output:

After running the code, you will see that it holds for 3 seconds after running the first two lines and then runs the rest of the 2 lines. Why does it happen? It's because of the Synchronous behavior of Javascript.

How is the code (Javascript) run? (Please, go through this animation to understand how javascript code runs in the browser.)

As you can see, there is a "Call Stack" and a "Console" in the browser. Basically, whenever the browser receives a javascript file, it calls a "main function" {main()} in the "call stack". Then it starts parsing through the full code base step by step.

First of all, there is a function at the beginning of the file. As the function is not called yet, it will not be called in the "call stack" in the first place. After the function, there is a "console.log function". So it will be called in the "call stack" and then it will be executed and "Taking order from customer1" will be printed in the console.

After the "console.log", the "processOrder" function (that is in the beginning) is called. So, it will be added to the "call stack" and execution of it will be started. Inside the function, there is a "console.log" function. So, it will be executed first. After that, there is a while loop that runs for 3 seconds. And for 3 seconds, the browser lags. Whenever 3 seconds is over, the browser starts executing the codes that are ahead. Indeed, when all the codes inside the "processOrder" function are executed, it will be popped from the stack.

And the same way, the rest of the codes will be executed. Whenever all the codes inside of a javascript file are executed, the "main()" function will be popped from the call stack.


Why is Asynchronous Javascript needed?

As you saw, the browser lagged for 3 seconds whenever the "while loop" was executed. It happened because of the "Synchronous Behavior" of javascript. And javascript by default is synchronous.

Now, imagine you had to fetch some data using API from a server, and the server had a problem for some reason and couldn't give you data and lagged for a few seconds. Because of the synchronous behavior of javascript, the browser would also be lagged and wouldn't move forward.

This is why we need "Asynchronous Javascript". Asynchronous Javascript basically refers to the way of writing javascript codes that follows an Asynchronous manner.


Asynchronous Javascript:

Now run this code and observe the output:

Output:

If you observe closely, you will see that everything that is inside of the "setTimeOut" function was executed in the end.

Why does it happen? => It happens because the function "setTimeOut()" is asynchronous and it doesn't block the whole code base while it's running.

How is the code processed in the browser? => (Follow the following video)

As you can see, the codes run the same as before. But there are 2 new features in it. One is "Web API" and the other one is "Callback Queue".

"Call Stack" basically moves (throws) the "asynchronous functions" to the Web API. And "Web API" picks up the "asynchronous functions" from the "Call Stack" and keeps it as long as the functions take. When it's time, "Web API" moves (throws) the functions to the "Callback Queue" and "Callback Queue" receives the functions, executes them, and throws the statements/functions that are inside of the functions to the "Call Stack". Then "call stack" executes them.

Asynchronous functions execution process:

| Call Stack ----> Web API (Waits here for a while) ----> Callback Queue ----> Call Stack |

For better understanding, play around this website: Loupe


Here, we used a function called "setTimeOut" which is an "asynchronous function". Everything was good. But a problem happened. That is we can't control the flow of the program as we want. like- " 'cooking completed' was being executed in the end". That's a big problem that we need to deal with.

So, to deal with that, a feature called "callback function" was introduced in JavaScript. Using "callbacks", we can control the flow of a program and a programmer gets more flexibility.

Callbacks:

Output here:

\> Taking Order from customer1

\> Processing order for customer1

\> Order processed for customer1

\> Order served

\> Cooking completed

But we don't want an Output like this. Here, the flow isn't controlled. We want an output like this:

\> Taking Order from customer1

\> Processing order for customer1

\> Cooking completed

\> Order processed for customer1

\> Order served

Let's fix this step by step. Let's fix the flow and make the code more flexible.

Write down this code:

Here I have converted the codes into little pieces and blocked them into functions. And have taken "Order processed for customer1" into the "setTimeOut" function so that the statements inside the "setTimeOut" are executed at the same time.

Now add these extras to your code:

Here, in each function, I have called the following function that I want to execute after the function. like- I want the "processOrder()" to execute after "takeOrder()" and I want the "orderServe()" to execute after "processOrder()". Also, I want to execute "orderServe()" when "setTimeOut()" is executed.

Now, what if we wanted to control the flow from outside of the functions?

If you run the code, you will see our expected outputs.

A JavaScript callback is a function that is to be executed after another function has finished execution. Any function that is passed as an argument to another function so that it can be executed in that other function is called a callback function.

In the last portion of the code (marked red), functions that are passed, are callback functions.

Now, there is a little problem using callback functions when there are too many functions. like-

Imagine, you have a few more functions. Then you have to add more callbacks here. Then, it will be unreadable for a programmer and hard to debug. And, this problem is called "Callback Hell".

So, to solve this problem, a feature/mechanism named "promise" was introduced in Javascript.


Promise:

The promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It is an asynchronous feature of JavaScript.

A Promise is in one of these states:

  • pending: initial state, neither fulfilled nor rejected.

  • fulfilled/resolved: meaning that the operation was completed successfully.

  • rejected: meaning that the operation failed.

      const meeting = new Promise((resolve, reject) => {
        //========== A promise is made ===============
    
          resolve(something)  //==== To resolve the promise ====
    
         reject(somthing)       //==== To reject the promise =====
      })
    
      meeting
          .then((res) => {
          //========= If the promise is resolved ===============
      })
           .catch((err) => {
         //========= If the promise is rejected ===============
      })
    

    Here,

    • A new Promise object (by using "new Promise") is created. The Promise constructor takes a function (executor) that has two parameters: resolve and reject.

    • If hasMeeting is false: The executor creates a meetingDetail object containing details about the meeting. The promise is resolved with the meetingDetail object using resolve(meetingDetail), indicating that the promise has been fulfilled successfully.

    • If hasMeeting is true: The promise is rejected using reject(new Error('Meeting already scheduled')), which indicates that the promise has failed with an error.

    • The 'then' method is called when the promise is successfully resolved. The resolved value (in this case, meetingDetail) is passed to the callback function inside 'then'. This callback logs the meeting details to the console.

    • The 'catch' method is used to handle any errors if the promise is rejected. If the promise is rejected, the error object is passed to the callback inside 'catch'. This callback logs the error message to the console.

What if "hasMeeting = true"?

Promise Chaining:

The code handles the scheduling of a meeting and then adds that meeting to a calendar, using a series of promises to manage the asynchronous operations. Promise chaining occurs when the then method of a promise returns another promise, allowing multiple asynchronous operations to be performed in sequence. Each then in the chain waits for the previous one to be resolved before executing. This approach is useful for managing a sequence of dependent asynchronous tasks.

Explanation:

  • The 'addToCalender' Function:

    • This function takes meetingDetail as an argument and returns a new promise.

    • Inside the promise, it creates a string calendar.

    • The promise is resolved with this string.

Promise Chaining:

  • **First '**then': When the 'meeting promise' is resolved, the result (meetingDetail) is passed to the addToCalender function. addToCalender returns a new promise, which is resolved with the string 'calender'.

  • **Second '**then': The resolved value from addToCalender (the calendar string) is passed to the next 'then' method, which logs the message to the console.

  • Catch Block: If any of the promises are rejected, the catch method handles the error and logs the error message to the console.

Promise.resolve() :

If you don't want to 'reject' anything into a "Promise", then you can use "Promise.resolve()" to resolve instead of creating a full "Promise".

Promise.all() :

We have two promises. And to see the resolves from 2 promises, this is how we need to write the code. But there is a better way:

By using "Promise.all([...])", you can resolve all the promises simultaneously.

Promise.race() :

It shows the 'resolve' of the promise that is resolved first. Here because of 'setTimeOut()', promise2 takes time to be executed and is executed after promise1.


Async-Await:

(Turns any function into an Asynchronous Function) | Async function basically returns Promise. And Await works as "then". | 'Async' and 'Await' are used simultaneously.

Asyn => Promise | Await => .then |

Output: Meeting added to calender: Engineering at Zoom at 10:00 AM |

Here, we have just written the previous "Promise Chain" code using async-await. We've declared an async function called "myMeeting" and inside of it, we controlled the flow of the program. First, we executed the meeting function and then we executed the "addToCalender" function.

Now, in this case, How do we catch errors?

This is how we do it:

So, that's all!


Keep in mind: Try to code in an "Asynchronous" way to make the code more efficient and flexible. Try using "Async-Await" instead of callbacks and promises.


THANK YOU!

Contact Me: LinkedIn

0
Subscribe to my newsletter

Read articles from Mohtasim Ahmed directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mohtasim Ahmed
Mohtasim Ahmed