Asynchronous JavaScript: Callback, Promise, and Async/Await

Before moving to asynchronous JavaScript let's understand the difference between synchronous and asynchronous.

Synchronous and Asynchronous in JavaScript

JavaScript is a synchronous, single-threaded programming language. It means code is run after one another. Imagine the queue line in the canteen, till the first person's order is not completed next person doesn't get their order.

Let's see an example:

console.log('JavaScript')
console.log('Is')
console.log('Synchronous')

//output
//JavaScript
//Is
//Synchronous

So there is a disadvantage to synchronous, what if after printing the Javascript we perform an action where it will take a certain amount of time to finish. It will block the execution.

Here asynchronous was introduced, where code is executed in parallel and does not block the execution.

To understand more about asynchronous we will study Callback, Promise, and Async/Await.

What are callbacks?

A callback is a function that is passed into another function as an argument and invoked to perform an action.

Let's take the example of setTimeout()

console.log('1')
setTimeout(() => console.log('2'),1000)
console.log('3')
//output:
//1
//3
//2

In the above example as you can see setTimeout is taking another function as an argument and time as an argument after which the function will invoke. setTimeout was executed parallel without blocking the execution of code.

Even the callback has the drawback of the callback being nested creating a pyramid structure that can be hard to maintain and is also called callback hell.

Promise in JavaScript

Ummm, Promise is how you go to any mobile brand site and they promise that if you subscribe they will provide the device to the subscriber first on the launch, and even due to any issues, the launch does not happen they will notify the early subscriber.

The constructor syntax for a promise object is:

let promise = new Promise(function(resolve, reject) {
  //code
});

Promise has two callback functions as arguments that are resolve() and reject().

The initial state of the promise is pending, if the request is successful we would resolve and the state is changed to fulfilled but if there is an error promise is rejected and the state is rejected.

const flag= true;

let learningPromise= new Promise(function (resolve, reject) {
    if (flag) {
        resolve("Promise Resolved");
    } else {
        reject("Promise Rejected");
    }
});

console.log(learningPromise)

//output: Promise Resolved

Promise chaining helped in resolving the problem we had in callback hell.

what is promise chaining?

As we saw earlier in a callback, if we have a nested callback it's hard to maintain. Promise chaining is helpful when we have to handle more than one asynchronous task one after the other.

We can perform an action after the request is resolved or rejected by promise using .then() and .catch()

const flag = true;

let learningPromise = new Promise(function (resolve, reject) {
    if (flag) {
        resolve("Promise Resolved");
    } else {
        reject("Promise Rejected");
    }
});

learningPromise.then(() => {console.log('Learning Promise chaining')})
.then(() => {console.log("You can call multiple functions this way.")})
.catch((error) => {console.log(error)})

// output
//Learning Promise chaining
//You can call multiple functions this way.

The then() method is called when the promise is resolved successfully. We can chain multiple then method.

const flag = false;

let learningPromise = new Promise(function (resolve, reject) {
    if (flag) {
        resolve("Promise Resolved");
    } else {
        reject("Promise Rejected");
    }
});

learningPromise.then(() => {console.log('Learning Promise chaining')})
.catch((error) => {console.log(error)})
//output : Promise Rejected

The catch() method is called when the promise is rejected.

To understand more and write in simpler ways JavaScript introduces async/await. Next, we are going to learn async/await.

Async/Await in JavaScript

With the help of the Async keyword, we can make the function asynchronous and return Promise. Async/await allows you to write code that appears to execute synchronously, but still performs asynchronously.

The syntax of the async function is:

const functionName = async (parameter1, parameter2) {
    // statements
}

Example:

const asyncDemoFunction = async () => {
    console.log("Async Fucntion");
    return Promise.resolve("Promise Resolved");
}

In the above example asyncDemoFunction, printing Async Function and return a promise. Since it's returning a promise we can use chainining method like the one below:

const asyncDemoFunction = async () => {
    console.log("Async Fucntion");
    return Promise.resolve("Promise Resolved");
}
asyncDemoFunction().then((response)=>{
console.log(response);
)
//output:
//Async Function
//Promise Resolved

Await keyword is used with the async function to wait for asynchronous operation.

The syntax for await:

const functionName = async (parameter1, parameter2) => {
    let reponse = await promise;
}

Example for await:

    const waitingForResponse = () =>{
        return new Promise(resolve => {
            setTimeout(() => {
            resolve('resolved');
            }, 2000);
        });
    }
    asyncDemoFunction = async () =>{
        let result = await waitingForResponse(); 
        console.log(result);
        console.log('After result');
    }
    asyncDemoFunction();

//output
//resolved
//After result

In the above exampleAfterresult only be printed after the promised value is available to the result.

In promise we had a catch method to handle errors when promises get rejected but how we will handle this in async/await? Lets learn with help of an example:

const waitingForResponse = () =>{
        return new Promise((resolve,reject) => {
            setTimeout(() => {
            reject('rejected');
            }, 2000);
        });
    }
    asyncDemoFunction = async () =>{
        try{
        let result = await waitingForResponse(); 
        console.log(result);
        console.log('After result');
       }
        catch(error){
        console.log('Promise',error)
        }

    }
    asyncDemoFunction();

//output
//Promise rejected

To handle errors in async/await, we have used the try/catch block.

Summary:

  1. JavaScript is synchronous, in which the interpreter runs code one by one

  2. Asynchronous JavaScript, in which code can be executed simultaneously.

  3. The callback function is passed as an argument to another function. Callback run after the task has been completed.Example setTimout(()=>{console.log('Happy Coding')},2000}

  4. Callback hell is where callback is being nested and it's hard to maintain and read.

  5. A promise can be in the Initial state: pending, fulfilled, or rejected. You can use then() and catch() methods over-promise. For example, If I score more than 80% on the exam then I ask for a trip otherwise I have to study hard. Promise also fix the callback hell issue.

  6. The Async keyword is used with the function to make it perform the asynchronous operation and return the promise.Async/await allows you to write code that appears to execute synchronously, but still performs asynchronously.