Async JS: Callbacks to Promises
Introduction
As we already know JavaScript is a single threaded language, which means its nature is blocking and main thread will be blocked at a single task at any point of time if that task takes more time, like file read or network calls which can take some time for fetching the data, whenever these tasks are executed the browser could be blocked and will be unresponsive for that amount of time.
BlockingCode
const fs=require("node:fs");
try{
const file=fs.readFileSync("path/to/file","utf-8");
}
catch(error){
console.error(error);
}
Here we are using synchronous function, which means your main thread will be blocked until the whole file is read.
We can solve this problem using callbacks
Callbacks
Callbacks are the functions which are passed to another function which are called Higher order function (HOF).
We used to use these callbacks for not blocking the main thread, Just passing a callback to a HOF for making our code Asynchronous i.e not blocking the main thread for operations which can take more amount of time, like..
const fs=require("node:fs");
try{
fs.readFile("path/to/file","utf-8",(error,data)=>{
if(error){
console.error(error);
}else{
console.log(data);
}
});
}catch(error){
console.error(error);
}
Here we are using fs module of node to read file asynchronously, what happens is that if any asynchronous code is running the main thread will pass it to WEB APIs in browser or libuv library in node and move to the next line of code. and whenever the Asynchronous task is completed and call stack is empty, the callback which is passed previously is executed. The callback passed above is a error first callback, where first argument of the callback is an error, and second is data fetched by the asynchronous function. Post completion of that task you can perform any operation on that data in the callback function. Callbacks are good but still have drawbacks.
Drawbacks of callback
1. Inversion of Control
2. Callback Hell
Because of these drawbacks promises were invented.
Promise
Promise is an object which will eventually fulfills or gets rejected, it has three stages pending (initial stage), fulfilled , rejected if the asynchronous task is completed it gets fulfilled and if there is an error occured during the promise gets rejected.
const fs=require("node:fs/promises");
fs.readFile("path/to/file","utf-8")
.then((data)=>{
console.log(data)
})
.catch((error)=>{
console.error(error)
});
Here a promise based function is called which will return a promise. And this promise is what you have to handle using then, catch and finally
.then will require a callback and the argument is passed or not is dependent if the resolve() called inside the callback creation is called using argument or not, and if the promise gets fulfilled it will move to the .then(). It will return a new promise with resolved value which you will pass from the callback passed to .then() for the purpose of .then chaining.
.catch will also require a callback where you can handle the error and also return a new promise for further chaining
.finally is used for cleanup things which you want definitely to execute no mattter what, It also returns a new promise.
Subscribe to my newsletter
Read articles from Anitya Sharma directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by