Handling Asynchronous Function is Crucial Part of Any WebDeveloper in 2024

 


First question anyone should ask is what is Asynchronous function and how it is different from synchronous function and what it look like.

We will discuss this topic in terms of JavaScript.

What is Synchronous functions?

As the name suggests synchronous means to be in a sequence, i.e. every statement of the code gets executed one by one. So, basically a statement has to wait for the earlier statement to get executed.

What is Asynchronous functions?

Javascript is a single-threaded language, which means that function that deals with things like input-Ouput, Sockets and the network, in general, would block the main thread when executed. To have the ability to write concurrent code that does not block the main thread with tasks that can be slow(needs some time to finish), JS uses what is called the Event Loop. So, an asynchronous function is just a function that can be put in a queue and have the results of the function checked in later, without blocking the main thread.

So in Synchronous function main thread is always blocked/waiting for result and its not the case in Asynchronous function.

Lets differentiate between synchronous and asynchronous function with example code which simulate the scenario of working of synchronous and asynchronous function.

Can you guess the output of below lines of code?

setTimeout is a built-in method in JavaScript that takes a function and time as an argument and will execute the function after given time, so it will schedule the task to execute the function after given time. 

index.js

console.log("Before");
setTimeout(() => {
    console.log("It took total 5 secs to fetch data from api xyz.");
}, 5000);
console.log("After");

First the message “Before” will be printed then setTimeout will schedule to execute the given function after 5 secs, next the message “After” will be printed, then after exactly 5 secs the message “It took total 5secs..” will be printed. Note that only one thread is used during execution of code.

Output:

Can you find which function in above example simulated asynchronous function?

Yes! The answer is setTimeout().

Ok, lets play one more. You have to guess the output.

index.js

const product = getProduct(5);
console.log(product);

function getProducts(id) {
    setTimeout(() => {
        console.log("fetching data from database...");
        retun { productName: 'Hi-Lighter' }; 
   }, 5000);
}

Did you get the output?

If not. lets discuss, here we have “getProduct” function to get the product object from database. After the “getProduct” function  called, it will search for return statement so that it can get the product object. But What we are returning from “getProduct” function will not be available at the time of calling “getProduct” because in this function we are calling “setTimeout” to schedule a task for future. “getProduct” when called will return “undefined” so console.log() will print “undefined” and after 5 secs the setTimeout function gets executed so “fetching data..” message will be printed.


 

Problem with Asynchronous.

This is a problem of asynchronous function Because when accessing a database the result is not available immediately it may take few milli-secs or secs, But we need to access the result whenever it is ready So question is how to do so?

Well there are three way to access it

  1.      Callbacks
  2.      Promises
  3.      Async & await

 

📌    Callbacks

A function that is passed to another function as a parameter is a callback function. Callbacks make sure that a function is not going to run before a task is completed but will run right after the task has completed. It helps us develop asynchronous JavaScript code and keeps us safe from problems and errors.

The way to create a callback function is to pass it as a parameter to another function, and then to call it back right after the result of asynchronous function is ready.

So lets convert our above code so that it can accept data whenever received in future.

we are passing anonymous function as argument of function “getProduct”.

We will name this anonymous function as “callback”, Now we are calling the callback function inside the setTimeout function because it is place where our result would be ready in future.

Can you guess the output now? 

index.js

const product = getProduct(5, function(product) {
    console.log(product);
});
console.log(product);

function getProducts(id, callback) {
    setTimeout(() => {
        console.log("fetching data from database...");
        callback({ productName: 'Hi-Lighter' }); 
   }, 5000);
}

“getProduct” function scheduled to execute after 5secs so here thread is free, thread will print on console “undefined” from line 4, then after 5 secs line 9 is executed and callback function is called with object as argument and now on line 2 we have access to the object and product object got printed.

Output:


 

Why callback is not a good practice ?

Lets see the dark side of callback which is also famously called as “callback hell” by developers in industry.

To show the dark part of callback in above code I have added simulation of one more async function to get the price of product from amazon API(application programming interface), here you will feel the hell part of callback.

index.js

const product = getProduct(5, function(product) {
    console.log(product);
    getPrice(function(item) {
        console.log(`Price of ${item.name} is : ${item.price}`);
    })
});
console.log(product);

function getProducts(id, callback) {
    setTimeout(() => {
        console.log("fetching data from database...");
        callback({ productName: 'Hi-Lighter' }); 
   }, 5000); 
} 

function getPrice(callback) {
    setTimeout(() => {
        console.log("calling amazon api to get price...");
        callback({ productName: 'Hi-Lighter', price: '50' }); 
   }, 1000); 
} 

Output:


So here you see the nested calling of function, In real world scenario it can be worse case as there can be lot of functions you need to call in nested fashion So then 

what is Solution?

We can make it more clear by replacing all the anonymous function with named functions, But it also creates lots of confusion so lets see How to handle this with Promises.

📌    Promises

It is powerful JavaScript tool to deal with asynchronous code.

Promise is an object that holds the result of an asynchronous function. When an asynchronous operations completes it either return result with value or an error.

Promise have 3 state, so it can be at any one state at a time.

Pending State: When we create a promise object it is in this state. And it will perform asynchronous operation.

Fulfilled State: When an asynchronous operation get completed successfully our promise will be in this state and we get some value in return.

Rejected State: If something went wrong during the execution of that asynchronous operation then  our promise will be in rejected state and we get error in return.

 

Now lets see how we can create our first promise object.

Promise constructor accept function with 2 parameter namely “resolve” & “reject”, inside this function we will perform our asynchronous operation and when it get completed either it results value or error.

index.js

const p1 = new Promise(function(resolve, reject) {
    setTimeout(() => {
        console.log("We perform asynchronous operation here!");
   }, 2000);
}

so now we need to pass the result to the consumer of this promise and we do that using parameter resolve & reject.

"resolve" is a function use to pass the generated value by asynchronous operation to the object of type Promise. When it is called state of promise changes from pending to fulfilled.

"reject" is a function use to pass error while performing asynchronous operation to the object of type Promise. Here state of promise changes from pending to rejected.    

To consume the passed result we have two method namely “then” & “catch”, lets see how to use them.

“then” is use to get the value from asynchronous operation which was passed by resolve function and we have to tell “then” that what to do next.

“catch” is use to catch any errors which was passed by reject function and we have to tell “catch” that what to do next.

Lets apply promise to our asynchronous simulation to fetch product data from database and getting price of same product from amazon api.

index.js


const getPrice = (productName) => {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            console.log("calling amazon api to get the price...");
            resolve({productName: "Hi-Lighter", price: "50"});
        }, 2000);
     } 
} 

const getProduct = (id) => {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            console.log("fetching data from database...");
            resolve({productName: "Hi-Lighter"});
        }, 1000);
     } 
} 

// we have used method chaning here
getProduct(5) // is returning a promise object let say "A"
    .then(product => getPrice(product.productName)) // is returning a promise object let say "B"
    .then(item => console.log(`Price of ${item.name} is : ${item.price}`))
    .catch(err => console.log('ERROR: ', err.message));

Here we start by calling “getProduct” which returns a promise object(“A”) containing “productName” as result using resolve function, try to recall that after passing value or error to consumer, to consume the result we have to use “then” or “catch” method. So we are consuming the value of promise object “A” at line 3.

Now that we received product data we are calling “getPrice” which returns another promise object(“B”) containing “name” & “price” as result using resolve function, to consume the result of promise “B” we have chained another “then” method at line 4 and we are telling “then” to display the output in console.

Note that we have used only one catch to handle all the errors in result of promises.

Output:


Lets see few cool methods of class Promise that are useful

1.     👉    Promise.all()

If we want to show output to user only if all asynchronous operations are completed successfully or resolved we can use ‘all’ method.

Promise.all([promise1, promise2, promise3, ….., promisen])
    .then(finalResult => console.log(“All Promises are resolved”))
    .catch(err => console.log(“Error: “, err.message));

How it is different than normal case?

In normal case, the next async operation is started by thread only if first promise got resolved.

While here, thread don’t wait for promise tobe resolved once the work of thread for one async operation is completed then it start other async operation. So result is received one-by-one in future.

The  “finalResult” that we will receive will be in array form. Also, if any one promise among all is rejected then all the promises which were resolved also considered to be in rejected state, so “finalResult” which we will receive from Promise.all() will be an error message.

2.    👉     Promise.race

The “Promise.all()” is good but what if we need result as soon as first promise (promise which will take less amount of time to give result) out of “n” promises  get Completed with either resolved or rejected, in this case we uses “race” method.

Promise.race() method returns the result of first promise which is completed.

Can you guess the output of below code?

index.js

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("promise1 response");
    }, 5000)
})

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("promise2 response");
    }, 2000)
})

Promise.race([p1, p2])
    .then(finalResult => console.log(finalResult))
    .catch(err => console.log(“Error: “, err.message));

Here the tricky part is time, promise “p1” will return result after 4secs while promise “p2” will return the result after 2 secs, so as soon as “p1” ask the thread for 4 secs, thread will execute “p2” and “p2” will return the result after 2 secs till now “p1” is waiting for result to arrive let say from amazon API. So “Promise.race” will get the first result from “p2” So it will also consider “p1” as resolved and it will return the output of first completed promise i.e. “promise2”.

Output:

 


📌    Async & Await

It is build upon promises only, it help us to write our Asynchronous function in Synchronous function Fashion but under the hood its working is same as promises Because During Compile time it is converted into promises format that we have seen above.

Here we can use operator “await” before the function which returns promises

And we can get the result of that promise in future.

Whenever we use “await” operator we need to wrap that inside a function and we need to decorate that function with “async” modifier.

Lets implement our simulation of asynchronous function with “async-await”

Note that Unlike promise we can’t handle the error in  “async-await”, only way to handle it is via “try & catch” block. 

index.js

const getPrice = (productName) => {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            console.log("calling amazon api to get the price...");
            resolve({productName: "Hi-Lighter", price: "50"});
        }, 2000);
     } 
} 

const getProduct = (id) => {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            console.log("fetching data from database...");
            resolve({productName: "Hi-Lighter"});
        }, 1000);
     } 
} 

async function displayPrice() {
    try {
        const product = await getProduct(5);
const productApi = await getPrice(product.productName);

console.log(`Price of ${productApi.name} is : ${productApi.price}`); } catch (err) { console.log(err.message); } }

Output:



 

Comments

Popular posts from this blog

Host Your Node.js App on AWS EC2 Instance for Free in 2024

GitCommandsPro Your Guide to Essential Git Commands in 2024

SOAP Explained: With Javascript