Callbacks, Promises & Async/Await

By in Community Stories April 17, 2019

JavaScript is single threaded, blocking and synchronous. Operations in JavaScript will execute in a synchronous way. This means that JavaScript cannot handle concurrent requests and will execute the code sequentially, not parallelly. It is easy to think that JavaScript is only an imperative and object-oriented language, but it is actually a functional/imperative hybrid and an event-based language that provides object-oriented features via “prototypical inheritance”

Note: In this article, we will learn about callbacks, promises & async/await in JavaScript

Synchronous vs Asynchronous

Synchronous means the execution happens in a single event. It will only execute the next event once the previous event is finished.

In Asynchronous, the execution will never wait to complete, instead, it will execute all the events in a single go and multiple events will be in progress, hence making JavaScript non-blocking.

As you can see in the image, for synchronous execution the tasks will be executed one after the other. This means only one task will be in progress at a time and this will block the execution of the other tasks.

For asynchronous execution, all the tasks will be executed simultaneously, and it will be handled once the result of each task is available.

For instance, if we need to do some heavy lifting with expensive database queries and make an ajax request, execution for the next lines of code needs to wait until & unless it hasn’t returned any response back from the DB server. This makes your application less responsive and your application might catch performance issues.

The way of writing such an application with CPU intensive and heavy database queries can be solved by using asynchronous code. It will indicate to the user some progress while running the I/O operations in the background.

Let’s us an see an example of an asynchronous operation:

let Testfunc = () => {
   console.log('first')
   setTimeout(() => console.log('second'), 2000)
   console.log('third')
}
 
Testfunc()

The output for the above code is

first
third
second

Now, let us consider the below code

let Testfunc = () => {
   console.log('first')
   setTimeout(() => console.log('second'), 0)
   console.log('third')
}
 
Testfunc()

The output of the above code is

first
third
second

To explain this, we have some concepts in JavaScript:

Call Stack, Event Loop and Queue

Call stack is based on LIFO – last in first out, it will know which piece of the code is in the execution and which function it will call next. If you have a function inside a function, this will be on the stack and will wait for their turn to get executed.

The callback queue works together with the event loop in executing the callback functions at that moment. So this means that the non-callback functions will get executed first as they don’t have to wait and then the callback functions which will wait for their turn in the queue.

Callback

The callback function is a function which will get called after the execution of the first function and it will run the second function.

Callback Example:

function getUser(){
           setTimeout(() => {
               const userids = [10, 20, 30, 40];
               console.log(userids);
               setTimeout((id) => {
                   const user = {
                       name:'John Doe',
                       age: 25
                   };
                       console.log(`User ID : ${id} : User name : ${user.name}, User Age: ${user.age}`)
                       setTimeout((age) => {
                           console.log(user);                            
                       }, 1000, user.age);    
           }, 1000, userids[3]);
           }, 1500)
       }
      
      
 getUser();

The output of the above code is:

(4) [10, 20, 30, 40]
User ID : 40 : User name : John Doe, User Age: 25
{name: "John Doe", age: 25}

As you can see in this example, we have three callbacks nested inside one another, which means three chained ajax calls to get the data from the server. It might have more and more chained callback functions and this might get out of hand. This concept is called “Callback hell” in JavaScript.

From Callback Hell to Promises

A promise is an object which keeps track of whether the asynchronous event has happened already or not and determines what happens after the event has occurred. It provides two values, resolved or rejected. It can be in three states, fulfilled, pending or rejected. It helps to catch all the errors that occurred after rejection or attach a callback to the handle of the fulfilled value.

Before the event has happened the promise is pending, then after the event has happened it is called resolved. When the promise is successful and the result is available, then it will be fulfilled, but if it caught errors, then it will be called rejected

Promise Example,

const getUsername = userid => {
           return new Promise((resolve, reject) => {
               setTimeout((id) => {
                   const user = {
                       name:'John Doe',
                       age: 25
                   };
                   resolve({user_id: id, username: user.name, age: user.age});
               },1500, userid)
           })
       }

       const getUserage = userid => {
           return new Promise((resolve, reject) =>{
               setTimeout((id) =>{
                   const user = {
                       name:'John Doe',
                       age: 25
                   };
                   resolve({user_id:id, user_age: user.age})
               },1500, userid)
           })
       }

      const getuserIds =  new Promise((resolve, reject) =>{
           setTimeout(() => {
               resolve([10,20,30,40])
           },1500)
       });


       getuserIds.then((IDS)=>{
           console.log(IDS)
           return getUsername(IDS[3]);
       }).then((userObj) => {
           console.log(userObj)
           return getUserage(userObj.user_id);
       }).then((userage) => {
           console.log(userage)
       }).catch((erorr)=>{
           console.log(erorr)
       })

The output of the above code is:

Promise {<pending>}
(4) [10, 20, 30, 40]
{user_id: 40, username: "John Doe", age: 25}
{user_id: 40, user_age: 25}

In the above example, we have consumed our promises with the “then” and finally a “catch” to catch the error. We have also tackled the callback hell with promises.

From Promises to Async/Await

Async/Await is used to work with promises with asynchronous functions.

  1. Putting Async in front of the function expects it to return the promise. This means all async function have a callback
  2. Await can be used for single promises to get resolved or rejected and return the data or error
  3. Async/Await behaves like synchronous code execution
  4. There can be multiple awaits in a single async function
  5. We will be using try/catch construct, which makes async/await easy to handle synchronous and asynchronous code
  6. Async/Await helps you to deal with callback hell

Async/Await Example,

 const userfunc = async () => {
          
           try {
               const id = await getuserIds;
               console.log(id);
               const getusername = await getUsername(id[3]);
               console.log(getusername.username);
               const getuserage = await getUserage(id[3]);
               console.log(getuserage.user_age);
           } catch (error) {
               console.log(error);               
           }
       };

       userfunc();

The output of the above code is:

(4) [10, 20, 30, 40]
Promise {<pending>}
John Doe
25

In this async/await example, we have consumed the promises and we have called multiple awaits in a single async function.

Conclusion

In this article, we have learned the difference between synchronous and asynchronous JavaScript, and also learned how to use a callback, promises and async/await.

You can leave your responses in the forum and reach to me if you find any issues executing above code.