JavaScript is asynchronous by nature, which means certain operations like API calls, file reads, or database queries donβt block the main thread.
Promises are a powerful way to handle asynchronous tasks in a structured, readable way.
But often, we donβt just want one async taskβwe want multiple tasks executed one after another. Thatβs where Promise chaining comes in.
π What is Promise Chaining?
Promise chaining is the technique of executing multiple asynchronous operations in sequence, where each step depends on the result of the previous one.
Instead of writing nested callbacks (callback hell), we can chain .then() handlers.
π Basic Structure of Promise Chaining
firstPromise()
.then(result1 => {
console.log("Step 1:", result1);
return secondPromise(result1); // pass result to next
})
.then(result2 => {
console.log("Step 2:", result2);
return thirdPromise(result2);
})
.then(result3 => {
console.log("Step 3:", result3);
})
.catch(error => {
console.error("Error caught:", error);
});
π Example 1: Simple Chaining
function step1() {
return Promise.resolve("Step 1 completed β
");
}
function step2(prev) {
return Promise.resolve(prev + " β Step 2 completed β
");
}
function step3(prev) {
return Promise.resolve(prev + " β Step 3 completed β
");
}
// Chain them
step1()
.then(result => step2(result))
.then(result => step3(result))
.then(finalResult => console.log(finalResult))
.catch(err => console.error(err));
π Output:
Step 1 completed β
β Step 2 completed β
β Step 3 completed β
π Example 2: With Delays (Simulating API calls)
function fakeAPI(name, delay) {
return new Promise(resolve => {
setTimeout(() => resolve(`${name} finished after ${delay}ms`), delay);
});
}
fakeAPI("Task 1", 1000)
.then(res => {
console.log(res);
return fakeAPI("Task 2", 1500);
})
.then(res => {
console.log(res);
return fakeAPI("Task 3", 500);
})
.then(res => console.log(res))
.catch(err => console.error(err));
π Output (with delays):
Task 1 finished after 1000ms
Task 2 finished after 1500ms
Task 3 finished after 500ms
β‘ Execution Flow of Promise Chaining
- First async task starts.
- When it resolves, its result is passed to the next
.then(). - If any
.then()returns a promise, execution waits for it to finish. - If an error occurs,
.catch()is triggered. - Finally,
.finally()runs (success or failure).
π‘ Example 3: Adding Error Handling
function riskyTask() {
return new Promise((resolve, reject) => {
const success = Math.random() > 0.5; // random pass/fail
success ? resolve("Success π") : reject("Failure β");
});
}
riskyTask()
.then(res => {
console.log("Step 1:", res);
return "Moving to step 2";
})
.then(res => {
console.log("Step 2:", res);
return riskyTask();
})
.then(res => console.log("Step 3:", res))
.catch(err => console.error("Caught error:", err))
.finally(() => console.log("Process finished π"));
π₯ Advanced: Returning Non-Promise Values
If you return a value (not a promise) inside .then(), it will automatically be wrapped in a resolved promise.
Promise.resolve(10)
.then(num => num * 2) // returns 20
.then(num => num + 5) // returns 25
.then(result => console.log("Final:", result));
π Output:
Final: 25
π Promise Chaining vs. async/await
The same process using async/await looks cleaner:
async function processTasks() {
try {
const res1 = await fakeAPI("Task 1", 1000);
console.log(res1);
const res2 = await fakeAPI("Task 2", 1500);
console.log(res2);
const res3 = await fakeAPI("Task 3", 500);
console.log(res3);
} catch (err) {
console.error("Error:", err);
} finally {
console.log("All tasks done π");
}
}
processTasks();
β FAQs on Promise Chaining
Q1. Do promises run in parallel or sequential in chaining?
π In chaining, they run sequentially, one after another. For parallel, use Promise.all().
Q2. What happens if I forget to return in .then()?
π The next .then() wonβt receive the previous result (undefined will be passed).
Q3. Can I mix synchronous and asynchronous code in chaining?
π Yes, any return value will be wrapped in a promise automatically.
Q4. Difference between .then().catch() vs try...catch?
π .then().catch() is promise-style, while try...catch works with async/await.
Q5. Is .finally() always executed?
π Yes, whether success or failure, .finally() always runs.
π― Key Takeaways
For cleaner syntax β prefer async/await.βd use Promise.all() instead of chaining .then().
Promise chaining avoids callback hell.
Always return inside .then() to pass results forward.
Use .catch() for centralized error handling.
Use .finally() for cleanup (like closing DB connections).
