Understanding Promises In Javascript

Javascript is a Synchronous language. Meaning, that javascript executes sequentially from top to bottom. To make Javascript asynchronous or to make a part of javascript code to execute in asynchronous way we use promises. Let's understand this by some examples.
Synchronous & Asynchronous
Let's understand difference between synchronous and asynchronous code. Here since we currently don't know the promises we are going to simulate it by using timers. We will use timers to execute some part of code after some time.
Synchronous Code
console.log("Start");
function task1() {
console.log("Task 1 completed");
}
function task2() {
console.log("Task 2 completed");
}
task1();
task2();
console.log("End");
---------------------------------------------------------------------
OUTPUT:
Start
Task 1 completed
Task 2 completed
End
This is the synchronous code it executes line by line from top to bottom, but what if task 1 takes few seconds to get completed. Let's paint a scenario by imagining that task1 is calling a Database from another continent and bringing that data and then printing "Task 1 completed". We are going to simulate this by using setTimeout() in JS.
console.log("Start");
function task1() {
setTimeout(() => {
console.log("Task 1 completed");
}, 2000);
}
function task2() {
console.log("Task 2 completed");
}
task1();
task2();
console.log("End");
---------------------------------------------------------------------
OUTPUT:
Start
Task 2 completed
End
Task 1 completed
Now you can see the difference. Task 1 was printed at the last why? because JS never waits. Yes if your code takes sometime to execute JS run that code and move to another line.
Start
JS run timeout-> Timeout -> Starts -> After 2 sec -> Task 1 Completed
JS moves to next line
logs Task 2 Completed
End
Now to make this execute sequentially by waiting for the task1 to get complete we use promises in JS.
Promises in Javascript
Let's paint a scenario in which you have to do a DB call (and always remember Databases are always in another continent) so this call will take some time to bring data from DB to your machine and then we have to process that data and send it to user.
Let's understand this on canvas(flow chart):
Since we are not waiting for data to come we are processing some undefined value and then sending the undefined processed data to user. This don't make any sense. So here comes Promises.
Promises:
A promise is a placeholder for a value which we will get in the future. Tasks like API calls, DB calls, fileread these task takes time and these are executed by OS so getting value from these task takes time , so to store the value which we will get in future we use promise.
Since code inside promises take time to execute we need to have track of that code, for that we have states in promises. In simple words, to know that the code is executed successfully or with error or it is still executing we need states.
There are 3 states of Promises
Pending: This state represent that the promise is not completed yet.
Fulfilled: This state represents that the promise has been completed successfully.(resolve)
Rejected: This state represent that the promise has failed but completed. (reject)
Creating a Promise:
const dbCall = new Promise((resolve, reject) => {
//here you will write your asynchronous code
if(successfull){
resolve(data);
}else{
reject(error);
}
})
Consuming a Promise:
dbCall
.then(data => console.log(data)) //--> Executes when promise is resolve
.catch(error => console.log(errpr)) //--> Executes when promise rejects
Lifecycle of Promise:
Callback Hell
Promises were not created randomly, they were a solution to the chaos by deeply nested callbacks also known as callback hell.
Earlier when their was no concept of promises we used to use callbacks means function call inside function call and so on....
function call (DB_call) -> function call (processing_data) -> function call (sending processed data to user).
This is just a small example.
This is how actually a callback used to look
loginUser("divakar", "password", function(err, user) {
if (err) return console.log(err);
getUserProfile(user, function(err, profile) {
if (err) return console.log(err);
getCart(profile, function(err, cart) {
if (err) return console.log(err);
applyDiscount(cart, function(err, discountedCart) {
if (err) return console.log(err);
calculateTotal(discountedCart, function(err, total) {
if (err) return console.log(err);
processPayment(total, function(err, paymentInfo) {
if (err) return console.log(err);
placeOrder(paymentInfo, function(err, order) {
if (err) return console.log(err);
sendConfirmation(order, function(err, message) {
if (err) return console.log(err);
console.log("Success:", message);
});
});
});
});
});
});
});
});
The same code with promises
loginUser("divakar", "password")
.then(user => getUserProfile(user))
.then(profile => getCart(profile))
.then(cart => applyDiscount(cart))
.then(discountedCart => calculateTotal(discountedCart))
.then(total => processPayment(total))
.then(paymentInfo => placeOrder(paymentInfo))
.then(order => sendConfirmation(order))
.then(message => {
console.log("Success:", message);
})
.catch(err => {
console.log("Error:", err);
});
You can see the difference that's why we have a concept of promises.
Promise Chaining
Let's take the same example from above of login user to understand the promise chaining.
Promise chaining is for what to execute next just tie them into a chain using .then().
returning value from first then will become data for 2nd then by this you can process the data as many time you want.
If some error occurred the catch block in the end will catch the error and run the catch callback function.
Conclusion
Promises simplify asynchronous JavaScript by replacing messy callbacks with a clean and structured approach. They make code more readable, easier to manage, and help avoid callback hell through chaining and better error handling.
Overall, understanding Promises is essential for writing modern, efficient, and maintainable JavaScript code.

