When you're dealing with tasks that take time, like fetching data from a server or reading a file, you need a way to handle them without blocking your code. That's where Promises come in.
What is a Promise?
A Promise in JavaScript is an object that represents a value that may be available now, or in the future, or never.
It helps you handle asynchronous operations in a clean and readable way, avoiding callback hell.
You can think of it like ordering food at a restaurant, you get a receipt (the promise) that guarantees your food will either arrive successfully or something will go wrong.
A Promise can be in one of three states:
- Pending: The operation is still running (your food is being prepared).
- Fulfilled/Resolved: The operation completed successfully (your food arrived).
- Rejected: The operation failed (the kitchen ran out of ingredients).
Creating a Promise
You create a Promise using the new Promise
constructor which takes a function called the executor.
The executor function has two arguments:
resolve
: Call this when your async task is successful.reject
: Call this when your async task fails or errors out.
Syntax:
const myPromise = new Promise((resolve, reject) => {
// Do some async work
if (/* operation successful */) {
resolve("Success!");
} else {
reject("Error message!");
}
});
Example:
const coinFlip = new Promise((resolve, reject) => {
const random = Math.random();
if (random > 0.5) {
resolve("Heads!");
} else {
reject("Tails!");
}
});
console.log(coinFlip); // Promise { <pending> }
Here,
coinFlip
is a Promise that simulates flipping a coin.Math.random()
generates a random number between 0 and 1.- If it's greater than 0.5, you resolve with "Heads!".
- If it's 0.5 or less, you reject with "Tails!".
- When you log the promise immediately, it shows as
Promise { <pending> }
because the operation hasn't completed yet.
Handling Promises with .then() and .catch()
Once you have a Promise, you need to handle its result.
To handle the result from a Promise, you have .then
and .catch
:
.then()
: Runs when the Promise resolves (succeeds)..catch()
: Runs when the Promise rejects (fails).
Syntax:
promise
.then((result) => {
// Handle success
})
.catch((error) => {
// Handle failure
});
Example:
const coinFlip = new Promise((resolve, reject) => {
const random = Math.random();
if (random > 0.5) {
resolve("Heads!");
} else {
reject("Tails!");
}
});
coinFlip
.then((result) => {
console.log("Success:", result);
})
.catch((error) => {
console.log("Failed:", error);
});
// Output:
// Success: Heads!
// OR
// Failed: Tails!
Here,
- If the coin flip resolves with "Heads!", the
.then()
block runs. - If it rejects with "Tails!", the
.catch()
block runs. - Only one of them will execute, never both.
Practical Example
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
// Simulate an API call
setTimeout(() => {
if (userId === 1) {
resolve({ id: 1, name: "Shefali", email: "connect@shefali.dev" });
} else {
reject("User not found");
}
}, 1000);
});
}
fetchUserData(1)
.then((user) => {
console.log("User found:", user);
})
.catch((error) => {
console.log("Error:", error);
});
// Output:
// Promise { <state>: "pending" }
// After 1 second:
// User found: { id: 1, name: "Shefali", email: "connect@shefali.dev" }
Here,
fetchUserData
returns a Promise that simulates fetching user data.- It uses
setTimeout
to simulate network delay. - If
userId
is1
, it resolves with user data. - Otherwise, it rejects with an error message.
- The
.then()
and.catch()
handle the success and failure cases.
Chaining Promises
You can chain multiple .then
calls to handle multiple async steps in order. Each .then()
returns a new Promise.
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
// Simulate an API call
setTimeout(() => {
if (userId === 1) {
resolve({ id: 1, name: "Shefali", email: "connect@shefali.dev" });
} else {
reject("User not found");
}
}, 1000);
});
}
fetchUserData(1)
.then((user) => {
console.log("Got user:", user.name);
return user.id; // This becomes the value for the next .then()
})
.then((userId) => {
console.log("User ID is:", userId);
return `Processing user ${userId}`; // This becomes the value for the next .then()
})
.then((message) => {
console.log(message);
})
.catch((error) => {
console.log("Something went wrong:", error);
});
// Output:
// Promise { <state>: "pending" }
// After 1 second:
// Got user: Shefali
// User ID is: 1
// Processing user 1
Here,
- The first
.then()
receives the user object and returnsuser.id
. - The second
.then()
receives the user ID and returns a message. - The third
.then()
receives the message and logs it. - If any step fails, the
.catch()
at the end will handle it.
Note: If any .then
or the original Promise rejects, .catch
will run.
Promise.resolve() and Promise.reject()
Sometimes you want to create a Promise that's already resolved or rejected.
// Already resolved
const resolvedPromise = Promise.resolve("Already done!");
resolvedPromise.then((value) => {
console.log(value); // Output: "Already done!"
});
// Already rejected
const rejectedPromise = Promise.reject("Already failed!");
rejectedPromise.catch((error) => {
console.log(error); // Output: "Already failed!"
});
Here,
Promise.resolve()
creates a Promise that's immediately resolved with the given value.Promise.reject()
creates a Promise that's immediately rejected with the given reason.
When to use this:
- When you want to convert a regular value into a Promise.
- When you need to return a Promise from a function but the result is already known.
- For testing or mocking asynchronous operations.
Real-World Example
function fetchWeather(city) {
return new Promise((resolve, reject) => {
// Simulate checking if city is valid
if (!city || city.trim() === "") {
reject("City name is required");
return;
}
// Simulate API call
setTimeout(() => {
if (city.toLowerCase() === "london") {
resolve({
city: "London",
temperature: 22,
condition: "Sunny",
});
} else {
reject(`Weather data not found for ${city}`);
}
}, 1500);
});
}
fetchWeather("London")
.then((weather) => {
console.log(`Weather in ${weather.city}:`);
console.log(`Temperature: ${weather.temperature}°C`);
console.log(`Condition: ${weather.condition}`);
})
.catch((error) => {
console.log("Failed to get weather:", error);
});
// Output:
// Promise { <state>: "pending" }
// After 1.5 seconds:
// Weather in London:
// Temperature: 22°C
// Condition: Sunny
Here,
fetchWeather
simulates calling a weather API.- It first validates the input, if no city is provided, it rejects immediately.
- It uses
setTimeout
to simulate network delay. - If the city is 'London', it resolves with weather data.
- Otherwise, it rejects with an error message.
- The
.then()
displays the weather data. - The
.catch()
handles any errors that occur.
Handling Multiple Promises
Promise.all()
It runs multiple Promises in parallel and waits for all to complete.
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then((results) => {
console.log(results); // Output: [1, 2, 3]
})
.catch((error) => {
console.log("One of them failed:", error);
});
Note: If any Promise rejects, the entire Promise.all()
rejects immediately.
Promise.allSettled()
It waits for all Promises to complete, regardless of success or failure.
const promise1 = Promise.resolve("Success");
const promise2 = Promise.reject("Failed");
Promise.allSettled([promise1, promise2]).then((results) => {
console.log(results);
});
// Output:
// [
// { status: 'fulfilled', value: 'Success' },
// { status: 'rejected', reason: 'Failed' }
// ]
Common Mistakes
-
Forgetting to return values in
.then()
:// Wrong promise .then((data) => { processData(data); // Not returning anything }) .then((result) => { console.log(result); // undefined }); // Right promise .then((data) => { return processData(data); // Return the result }) .then((result) => { console.log(result); // Actual result });
-
Not handling errors:
// This can cause unhandled promise rejections fetchData().then((data) => { console.log(data); }); // Add .catch() to handle errors
-
Creating unnecessary Promises: If you already have a Promise, don't wrap it in another one.
// Don't do this function fetchData() { return new Promise((resolve, reject) => { fetch("/api/data") .then((response) => resolve(response)) .catch((error) => reject(error)); }); } // Do this function fetchData() { return fetch("/api/data"); }
Best Practices
- Always handle both success and failure cases with
.then()
and.catch()
. - Keep your Promise chains flat. Avoid nesting
.then()
calls inside other.then()
calls. - Return values from
.then()
to pass data to the next step in the chain. - Use
Promise.all()
when you need to wait for multiple independent operations. - Use descriptive error messages when calling
reject()
. - Consider using
async/await
for more readable asynchronous code.