promise in javascript
Before promises, we handled asynchronous operations using callback functions, but callbacks are not a good way to handle asynchronous operations.
Callbacks:
Callback functions are passed as arguments to asynchronous functions(fetchData) and are executed once the operation completes.
The function typically returns immediately, and the callback is invoked when the task finishes.
Example:
function fetchData(callback) {
setTimeout(() => {
const data = "Here is your data!";
callback(data);
}, 1000);
}
fetchData((data) => {
console.log(data); // Output after 1 second: "Here is your data!"
});
fetchData is an asynchronous operation that uses a callback function.
The setTimeout simulates a delay (like fetching data from a server).
Once the data is ready, the callback function is executed.
Example of Callback Hell:
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doAnotherThing(newResult, function(finalResult) {
console.log(finalResult);
});
});
});
- As you can see, the code becomes more and more indented as you add more asynchronous operations. This is hard to read, and even harder to maintain or debug.
Issuse with callback: 1. callbackhell and 2. inversion of control
Inversion control means
when we will pass one callback function inside of other callback function but dod’t know how the function behaves in this case we don’t have control for it .
This concept often arises when we pass a function (like a callback function) into another function, and we don't know exactly when or how that function will be executed.
Let's break this down with an example in JavaScript, where we pass a callback function into another function.
Example:
function fetchData(url, callback) {
// Simulating an async operation (like an API call)
setTimeout(() => {
console.log("Data fetched from:", url);
callback(); // We call the passed-in callback when done
}, 1000);
}
function processData() {
console.log("Processing the data...");
}
function main() {
console.log("Starting main function...");
fetchData("https://api.example.com/data", processData); // We don't control when processData is executed
console.log("Main function continues...");
}
main();
Explanation:
The main function calls fetchData and passes processData as a callback.
The fetchData function simulates fetching data from a URL (e.g., through an API call), and once the data is "fetched," it invokes the processData callback.
The key point of IoC here is that we do not have direct control over when processData is executed—it is executed after the data fetching process is complete (in this case, after a delay of 1 second).
Why is it Inversion of Control?
Control is inverted because we're passing control to the callback function (
processData
), and we don't know exactly when it will execute. We trustfetchData
to call it at the appropriate moment.The flow of the program is dictated by
fetchData
rather than directly by themain
function. Themain
function doesn't know whenprocessData
will be called or even what it does.
Then Why Promise?
The main reasons for using Promises is to avoid callback hell.
With just callbacks, the code can become deeply nested and difficult to manage, especially when you have multiple asynchronous operations that depend on each other. Here's an example of callback hell:
Now time to understand Promise
A Promise is like a "future value" — it's an object that represents the eventual result of an asynchronous operation.
There are three state in promise: 1. resolve 2. reject 3.pending
Creating an Order (createOrder function)
const cart = ['wine', 'cigarette', 'pan masla'];
function validateCart(cart) {
if (cart.length > 0) {
return true ;
}
return false;
}
function createOrder(cart) {
return new Promise((resolve, reject) => {
if (!validateCart(cart)) {
const err = new Error("Something went wrong: Cart is empty");
reject(err);
}
const orderId = "12345";
if (orderId) {
setTimeout(()=>{
resolve(orderId);
},5000)
}
});
}
function procedToPayment(orderId){
return new Promise((resolve,reject)=>{
resolve("payment sussesfully done!")
})
}
const p1 = createOrder(cart);
p1.then((orderId) => {
console.log(orderId);
return orderId// Logs the resolved orderId (12345)
})
.then((orderId)=>{
return procedToPayment(orderId)
})
.then(function(paymentInfo){
console.log(paymentInfo)
})
.catch(function (err){
console.log(err)
})
A Promise is created when you call createOrder(cart).
If the cart is empty, the promise is rejected with an error (reject(err)), meaning something went wrong.
If the cart has items, it creates an order ID and after 5 seconds (setTimeout), the promise is resolved with the orderId.
Proceed to Payment (procedToPayment function)
Once the order ID is successfully created, we proceed to the payment.
This function immediately returns a Promise that resolves with the message "payment successfully done!".
Chaining Promises
The following code chains the steps in a sequence using .then() to ensure the steps happen one after another:
const p1 = createOrder(cart);
console.log(p1);
p1.then((orderId) => {
console.log(orderId); // Logs the orderId (12345)
return orderId; // Passes the orderId to the next `.then()`
})
.then((orderId) => {
return procedToPayment(orderId); // Calls the payment function
})
.then(function(paymentInfo) {
console.log(paymentInfo); // Logs "payment successfully done!"
})
.catch(function (err) {
console.log(err); // If any error happens, catch it and log the error
})
Step 1: createOrder(cart) is called and returns a Promise (p1). If the cart is not empty, it resolves with an order ID after 5 seconds.
Step 2: .then((orderId) => { ... }) waits for the order creation (resolution of the Promise). When the order is created, the order ID is passed to the next step.
Step 3: The next .then() waits for the payment process to complete. Once the payment is done, it resolves with "payment successfully done!".
Step 4: .catch() catches any errors if the cart is empty or any other issue occurs during the process (for example, if something goes wrong when creating the order).
Understanding the Last .then() Block
const p1 = createOrder(cart); // Calls the createOrder function
console.log(p1); // Logs the promise object (which is still pending at this point)
p1.then((orderId) => {
console.log(orderId); // Logs the resolved orderId (12345)
return orderId; // Passes the orderId to the next .then() block
})
.then((orderId) => {
return procedToPayment(orderId); // Calls procedToPayment with the orderId
})
.then(function(paymentInfo) {
console.log(paymentInfo); // Logs the payment success message
})
.catch(function(err) {
console.log(err); // If any error occurs (e.g., cart is empty), this will be logged
})
.then(function(orderID) {
console.log("It will call if error occur or not");
});
Purpose of last .then() Block:
Last .then() block is called after the entire promise chain completes — whether the promises are resolved successfully or an error is caught in the .catch() block.
The key thing is that last .then() is not dependent on the result of the previous promises. It's effectively a "final" step in the promise chain that ensures something happens after the entire process is complete.
Now time understand promise api:
As per the JavaScript documentation, the Promise API always accepts an iterable.
Promise.all()
Promise.all() is used when you want to run multiple promises in parallel and wait for all of them to either resolve or for any one of them to reject.
It accepts an array of promises and returns a single promise that resolves when all promises resolve or rejects when any promise rejects.
How Promise.all() works:
All resolve: If all the promises inside the array resolve successfully, Promise.all() will resolve with an array of the results of each promise in the order they were passed.
Any reject: If any of the promises reject, Promise.all() will immediately reject and return the reason (error) of the first rejected promise.
Imagine you have a list of employee IDs and want to fetch details of all employees in parallel (say from an API). You could use Promise.all() for this.
Suppose we have three API calls to fetch employee details: p1, p2, and p3.
p1 takes 3 seconds, p2 takes 2 seconds, and p3 takes 1 second to finish.
- Promise.all() will wait for all three to finish and then return all the results together.
const getEmployeeDetails = (id) => { return new Promise((resolve, reject) => { const delay = Math.floor(Math.random() * 5000); setTimeout(() => { resolve(`Employee ${id} details fetched in ${delay / 1000} sec`); }, delay); }); }; // List of employee IDs const employeeIds = [101, 102, 103]; // Create an array of promises for all employees const promises = employeeIds.map(id => getEmployeeDetails(id)); Promise.all(promises) .then((results) => { console.log("All employee details fetched successfully:", results); }) .catch((error) => { console.log("An error occurred:", error); });
In this case:
getEmployeeDetails(101) might take 3 seconds to resolve.
getEmployeeDetails(102) might take 2 seconds to resolve.
getEmployeeDetails(103) might take 1 second to resolve.
When Promise.all() is used, it will wait for all three promises to finish. If they all succeed, it will log the results after the last one finishes (in this case, probably after 3 seconds, since that's the longest).
Any rejection example: Now, let's assume that one of the API calls fails (say p2 rejects with an error). In this case, Promise.all() immediately rejects and will not wait for other promises to resolve.
const getEmployeeDetails = (id) => {
return new Promise((resolve, reject) => {
const delay = Math.floor(Math.random() * 5000);
setTimeout(() => {
if (id === 102) {
reject(`Failed to fetch details for employee ${id}`);
} else {
resolve(`Employee ${id} details fetched in ${delay / 1000} sec`);
}
}, delay);
});
};
// List of employee IDs
const employeeIds = [101, 102, 103];
// Create an array of promises for all employees
const promises = employeeIds.map(id => getEmployeeDetails(id));
Promise.all(promises)
.then((results) => {
console.log("All employee details fetched successfully:", results);
})
.catch((error) => {
console.log("An error occurred:", error);
});
In this case:
The promise for employee 102 will reject with an error (Failed to fetch details for employee 102).
As soon as p2 rejects, Promise.all() will immediately reject and log the error. It won’t wait for p1 and p3 to finish.
So, the output would be:
An error occurred: Failed to fetch details for employee 102
Real-time Example (API Call):
Let's say you're working on an app where you want to fetch user data from multiple services at once, like fetching profile information, posts, and friends list for a user.
const fetchProfile = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Profile data for user ${userId}`);
}, 2000);
});
};
const fetchPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Posts for user ${userId}`);
}, 3000);
});
};
const fetchFriends = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Friends list for user ${userId}`);
}, 2500);
});
};
const userId = 123;
const userPromises = [
fetchProfile(userId),
fetchPosts(userId),
fetchFriends(userId)
];
Promise.all(userPromises)
.then(results => {
console.log("User details fetched successfully:", results);
})
.catch(error => {
console.log("Error fetching user details:", error);
});
Output
fetchProfile takes 2 seconds.
fetchPosts takes 3 seconds.
fetchFriends takes 2.5 seconds.
Promise.all() will wait for all of them to resolve and return the results once all promises are completed.
If any of the API calls failed (e.g., fetchPosts rejected), Promise.all() would immediately reject and stop the execution of the remaining promises, logging the error from the first rejected promise.
Promise.allSettled()
Let's assume we have 3 promises:
p1 takes 3 seconds to resolve.
p2 takes 2 seconds to reject.
p3 takes 1 second to resolve.
When we use Promise.allSettled([p1, p2, p3]), it will wait for all promises to complete. Even though p2 is rejected, it will still wait for p1 and p3 to finish before giving the final output.
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p1 completed successfully");
}, 3000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("p2 failed");
}, 2000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p3 completed successfully");
}, 1000);
});
// Using Promise.allSettled to wait for all promises to settle
Promise.allSettled([p1, p2, p3])
.then((results) => {
console.log("Results after all promises are settled:", results);
});
How It Works:
p1: Takes 3 seconds, and it resolves successfully.
p2: Takes 2 seconds, but it gets rejected with an error.
p3: Takes 1 second, and it resolves successfully.
Since we are using Promise.allSettled(), all promises will be completed (whether resolved or rejected). After 3 seconds, we will receive an array of results like this:
[
{ status: "fulfilled", value: "p1 completed successfully" },
{ status: "rejected", reason: "p2 failed" },
{ status: "fulfilled", value: "p3 completed successfully" }
]
Explanation of the Output:
p1: Resolved successfully, so the object contains "fulfilled" with the value key holding the resolved data.
p2: Rejected, so the object contains "rejected" with the reason key showing the rejection message.
p3: Resolved successfully, so the object contains "fulfilled" with the value key holding the resolved data.
So, Promise.allSettled() waits for all promises to finish, regardless of whether they resolve or reject, and returns the results with their respective statuses.
Real-Time Use Case Example:
Imagine you're making multiple API calls to fetch user data from different services. Some services might fail due to network issues, but you still want to collect the results from other services that succeed.
For instance:
Fetching user profile data from one service.
Fetching user posts from another service.
Fetching user friends list from another.
You want to ensure that you get the data from all the services, even if one of them fails.
const fetchProfile = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Profile fetched");
}, 3000);
});
};
const fetchPosts = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Failed to fetch posts");
}, 2000);
});
};
const fetchFriends = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Friends list fetched");
}, 1000);
});
};
// Use Promise.allSettled to handle multiple API calls
Promise.allSettled([fetchProfile(), fetchPosts(), fetchFriends()])
.then((results) => {
console.log(results);
});
Output
[
{ status: "fulfilled", value: "Profile fetched" },
{ status: "rejected", reason: "Failed to fetch posts" },
{ status: "fulfilled", value: "Friends list fetched" }
]
Making parallel requests to multiple APIs.
Collecting data from services where some might fail, but you want to process the successful responses anyway.
Not having to worry about one failing promise rejecting the whole process.
Promise.race()
Promise.race() only cares about the first promise that settles (either resolves or rejects).
The other promises that are still pending after the first one settles are ignored, even if they resolve later.
Let’s assume we have three promise p1, p2, and p3
Let's consider the following promises:
p1 takes 3 seconds to settle.
p2 takes 2 seconds to settle.
p3 takes 1 second to settle.
Now, when we run Promise.race([p1, p2, p3]), it will check which of these promises settles first, and retrun it resolves or rejects.
Example Code:
let p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve("p1 resolved"), 3000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve("p2 resolved"), 2000);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve("p3 resolved"), 1000);
});
// Race to see which promise settles first
Promise.race([p1, p2, p3]).then(result => {
console.log(result); // Output will be: "p3 resolved"
});
Output:
After 1 second, p3
settles first, so the output will be:
Why is it like a race?
Just like in a race, where the first person to cross the finish line wins, Promise.race() only cares about the first promise to settle. It doesn't matter if the other promises eventually resolve or reject — the result will always be from the first promise that finishes.
Promise.any()
Promise.any() is a method that is used to wait for the first promise in a group of promises to resolve (succeed).
If any of the promises in the given iterable (array, for example) resolves successfully, Promise.any() will return that resolved value.
If all the promises fail (i.e., they reject), it will return an AggregateError containing all the individual errors of the failed promises.
Example:
Let's break down your example with three promises:
p1 takes 3 seconds to complete.
p2 takes 2 seconds to complete.
p3 takes 3 seconds to complete.
let p1 = new Promise((resolve, reject) => {
setTimeout(() => reject("p1 failed"), 3000); // fails after 3 seconds
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve("p2 succeeded"), 2000); // succeeds after 2 seconds
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => reject("p3 failed"), 3000); // fails after 3 seconds
});
Promise.any([p1, p2, p3])
.then(result => {
console.log(result); // Will log "p2 succeeded" (because p2 resolves first)
})
.catch(error => {
console.log(error); // If all promises reject, an AggregateError is thrown
});
Here ,we are calling Promise.any([p1, p2, p3]). Here's how it works:
p1 takes 3 seconds, p2 takes 2 seconds, and p3 also takes 3 seconds.
Promise.any([p1, p2, p3]) will wait for the first promise that resolves. So, even though p1 and p3 take 3 seconds, p2 will resolve first because it takes only 2 seconds. This means p2's result will be returned as soon as it's fulfilled.
If any of the promises (p1, p2, or p3) is fulfilled (resolved), the result from that promise is returned. In this case, p2 will resolve first (after 2 seconds), and p2's result will be the one returned by Promise.any().
If all promises fail:
If all promises reject (i.e., none of them resolves successfully), Promise.any() will return an AggregateError.
This error will contain a list of all the rejection reasons for the promises that failed. For example:
- If p1, p2, and p3 all reject with errors, the result of Promise.any([p1, p2, p3]) will be an AggregateError object, containing all of the individual rejection errors from each promise.
Code Example:
let p1 = new Promise((resolve, reject) => {
setTimeout(() => reject("p1 failed"), 3000); // fails after 3 seconds
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve("p2 succeeded"), 2000); // succeeds after 2 seconds
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => reject("p3 failed"), 3000); // fails after 3 seconds
});
Promise.any([p1, p2, p3])
.then(result => {
console.log(result); // Will log "p2 succeeded" (because p2 resolves first)
})
.catch(error => {
console.log(error); // If all promises reject, an AggregateError is thrown
});