Promises are a fundamental part of modern JavaScript, introduced in ECMAScript 2015 (ES6). They provide a way to handle asynchronous operations more cleanly and predictably than traditional callbacks.
A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It's like a "placeholder" for a value that might not be available yet.
- Promises were created to solve callback hell (deeply nested callbacks in async code).
- They allow you to write asynchronous code that looks more like synchronous code (especially with async/await, but we'll focus on raw promises here).
- Promises are immutable once settled — their value or reason can't change after fulfillment or rejection.
Promise States
Every Promise goes through one of the three states(it starts in "pending" and moves to one of the other two):
- Pending: Initial state — the operation is ongoing, neither fulfilled nor rejected.
- Fulfilled(Resolved): The operation completed successfully, and the Promise has a value.
- Rejected: The operation failed, and the Promise has a reason (usually an Error object)
Creating a Promise
you create a Promise using the Promise constructor, which takes and executor funtion:
const myPromise = new Promise((resolve, reject) => {
// Async operation here...
if (success) {
resolve(value); // Fulfill with value
} else {
reject(reason); // Reject with reason (e.g., new Error("Failed"))
}
}
-
resolve(value): Settles to fulfilled. -
reject(reason): Settles to rejected. - the executor runs immediately(synchronously) when the Promise is created.
Handling Promise Outcomes
You attach handlers using methods (these return new Promises for chaining):
-
.then(onFulfilled, onRejected): Handles fulfillment (first arg) or rejection (second arg).- If fulfilled → calls
onFulfilled(value) - If rejected → calls
onRejected(reason)
- If fulfilled → calls
.catch(onRejected): Shortcut for.then(null, onRejected)— only handles rejection. Always use.catch— uncaught rejections are bad..finally(onFinally): Runs regardless of outcome (no access to value/reason) — useful for cleanup.
Chaining and Error Propagation
- Each
.then/.catch/.finallyreturns a new Promise, allowing chaining. - If a handler returns a value → next
.thengets that value. - If a handler throws an error or returns a rejected Promise → skips to next
.catch. - Unhandled rejections trigger a console warning (and in Node.js, can crash the process if not handled).
Here are some examples to understand how it works
Example 1: Basic Promise Creation and Resolution
A simple Promise that resolves after a delay (simulating async like setTimeout).
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Success! Data loaded.");
}, 1000); // 1 second delay
});
myPromise
.then(result => {
console.log("Fulfilled:", result); // "Fulfilled: Success! Data loaded."
})
.catch(error => {
console.error("Rejected:", error);
})
.finally(() => {
console.log("Finally: Operation complete."); // Always runs
});
- What happens: Promise starts pending → resolves after 1s → .then runs → .finally runs.
Example 2: Rejection and Error Handling
Simulate a failure.
const failingPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Failed to load data."));
}, 1000);
});
failingPromise
.then(result => console.log("Won't run:", result))
.catch(error => console.error("Caught error:", error.message)) // "Caught error: Failed to load data."
.finally(() => console.log("Cleanup done."));
-
Key:
.then's first handler skips on rejection →.catchhandles it.
Example 3: Chaining Promises
Chain multiple async operations.
function fetchUser(id) {
return new Promise(resolve => {
setTimeout(() => resolve({ id, name: "Alice" }), 500);
});
}
function fetchPosts(user) {
return new Promise(resolve => {
setTimeout(() => resolve(["Post 1", "Post 2"]), 500);
});
}
fetchUser(1)
.then(user => {
console.log("User:", user);
return fetchPosts(user); // Return a new Promise for chaining
})
.then(posts => {
console.log("Posts:", posts);
})
.catch(error => console.error("Error in chain:", error));
- Output: Logs user after 500ms → posts after another 500ms.
-
Propagation: If
fetchUserrejects → skips to .catch.
Example 4: Error in Chain
Throw inside a handler → propagates.
Promise.resolve(10)
.then(value => {
console.log("First:", value); // 10
throw new Error("Oops!"); // Throws → rejects next Promise
})
.then(value => console.log("Won't run:", value))
.catch(error => console.error("Caught:", error.message)); // "Caught: Oops!"
Code Playground
Key Concepts
Promise handlers (
.then/.catch) run in the microtask queue — higher priority than macrotasks (e.g.,setTimeout). They execute after current sync code but before next event loop tick.
async/await(ES2017) builds on Promises, making them look synchronous (e.g.,const value = await myPromise;).
Thanks for reading. Happy coding!!!
Top comments (0)