Skip to main content

JS - Promises Basics

· 6 min read

JavaScript Promises

JavaScript Promises simplify asynchronous programming by managing the execution and resolution of tasks that take time to complete.

A Promise in JavaScript has three states:

  • Pending → Initial state before resolution or rejection.
  • Fulfilled (Resolved) → Successfully completed, carrying a result.
  • Rejected → Failed operation with an error reason.

Chaining Promises

Correct Chaining

Promises allow sequential execution by passing results to the next .then() handler.

new Promise(resolve => setTimeout(() => resolve(1), 1000))
.then(result => {
console.log(result); // 1
return result * 2;
})
.then(result => {
console.log(result); // 2
return result * 2;
})
.then(result => {
console.log(result); // 4
return result * 2;
});

Incorrect Chaining

Each .then() operates on the same original promise without passing results forward.

let promise = new Promise(resolve => setTimeout(
() => resolve(1), 1000)
);

promise.then(result => { console.log(result); return result * 2 } ); // 1
promise.then(result => { console.log(result); return result * 2 } ); // 1
promise.then(result => { console.log(result); return result * 2 } ); // 1

// modified result value is not passed forward

Error Handling

Promise handlers have an implicit try...catch, automatically catching thrown errors.

new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!

is same as:

new Promise((resolve, reject) => 
reject(new Error("Whoops!"))
).catch(alert); // Error: Whoops!

Handling Errors in Chained Promises

Case 1: No Errors (Success Case)

new Promise((resolve, reject) => {
resolve("Success!");
})
.then(result => {
console.log(result); // Output: Success!
return "Continuing execution";
})
.then(msg => {
console.log(msg); // Output: Continuing execution
})
.catch(error => {
console.error("Error caught:", error); // Never executes
});
  • The first promise resolves successfully with "Success!".
  • The first .then() receives this value and logs "Success!".
  • The second .then() continues execution and logs "Continuing execution".
  • The .catch() never executes because there’s no error.

Case 2: Error Occurs (Handled by .catch())

If an error occurs at any stage of a promise chain, .catch() captures it. The execution resumes after .catch() with the next .then(), allowing for graceful recovery.

The execution of .then(), .catch(), and .finally() follows the order in which they are defined.

  • If an error occurs, it skips the remaining .then() blocks until a .catch() is found.
  • A re-thrown error continues down the chain until another .catch() handles it.
  • .finally() always runs, regardless of success or failure.
  • .finally() does not affect the promise chain's result.
new Promise((resolve, reject) => {
console.log("Step 1: Starting...");
resolve("✅ Step 1 Complete");
})
.then(result => {
console.log(result); // Output: ✅ Step 1 Complete
console.log("Step 2: Proceeding...");
return "✅ Step 2 Complete";
})
.then(result => {
console.log(result); // Output: ✅ Step 2 Complete

console.log("Step 3: Might fail...");
throw new Error("🔥 Step 3 Failure");
})
.catch(error => {
console.error("🚫 Caught an error in Step 3:", error.message); // error.message = "🔥 Step 3 Failure"

console.log("Handling error, but continuing...");
return "✅ Error handled at Step 3";
})
.catch(error => {
// This will be **SKIPPED** because the previous `.catch()` handled the error
console.error("Second Catch Block: Won't run!");
})
.then(result => {
console.log(result); // Output: ✅ Error handled at Step 3

console.log("Step 4: Another risky operation...");
throw new Error("💥 Step 4 Error");
})
.catch(error => {
console.error("🚫 Caught another error in Step 4:", error.message); // error.message = "💥 Step 4 Error"

console.log("❌ Re-throwing the error...");
throw error; // Re-throwing → Next `.catch()` will execute.
})
.catch(error => {
console.error("🚨 Second Catch Block (After Re-Throw):", error.message); // error.message = "💥 Step 4 Error"

return "✅ Error handled at Step 4";
})
.finally(() => {
console.log("Cleanup in Finally: This executes no matter what.");

return "This value is IGNORED";
})
.then(result => {
console.log(result); // Output: ✅ Error at Step 4 (NOT "This value is IGNORED")
console.log("🎉 All done!"); // Output: 🎉 All done!
});

Promise.all vs Promise.race vs Promise.any

  • Promise.all (Wait for all, fail if any fails) – Resolves when all succeed, but fails immediately if any reject.
  • Promise.race (First to settle wins) – Resolves or rejects with the first settled promise, whether success or failure.
  • Promise.any (First success wins, failures ignored) – Resolves with the first successful promise, ignoring rejections.
  • Promise.allSettled (Wait for all, never rejects) – Resolves with the results of all, whether fulfilled or rejected.
Example Code
const p1_1s = new Promise(resolve => 
setTimeout(() => resolve(1), 1000)); // Resolves in 1s

const p2_2s_Error = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Whoops!")), 2000)); // Rejects in 2s

const p3_3s = new Promise(resolve =>
setTimeout(() => resolve(3), 3000)); // Resolves in 3s

const p4_4s = new Promise(resolve =>
setTimeout(() => resolve(4), 4000)); // Resolves in 4s


// Promise.all - Waits for all to resolve
Promise.all([p1_1s, p3_3s, p4_4s])
.then(console.log)
.catch(console.error);
// Output after 4s: [1, 3, 4] (All resolved)

// Promise.all - Fails early if any reject
Promise.all([p1_1s, p2_2s_Error, p3_3s])
.then(console.log)
.catch(console.error);
// Output after 2s: Error: Whoops! (Fails early)


// Promise.race - First to settle (resolve or reject)
Promise.race([p1_1s, p3_3s, p4_4s, p2_2s_Error])
.then(console.log)
.catch(console.error);
// Output after 1s: 1 (Fastest promise wins)

// Promise.race - First to reject
Promise.race([p3_3s, p4_4s, p2_2s_Error])
.then(console.log)
.catch(console.error);
// Output after 2s: Error: Whoops! (First rejection wins)


// Promise.any - First successful promise (ignores failures)
Promise.any([p2_2s_Error, p3_3s, p4_4s])
.then(console.log)
.catch(console.error);
// Output after 3s: 3 (First success wins, failures ignored)


// Promise.allSettled - Waits for all, never rejects
Promise.allSettled([p1_1s, p2_2s_Error, p3_3s, p4_4s])
.then(console.log);
/* Output after 4s:
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: Error: Whoops! },
{ status: 'fulfilled', value: 3 },
{ status: 'fulfilled', value: 4 }
]
*/

Promise.all(promises) - All or Nothing

  • Waits for all promises to resolve.
  • If any fail, rejects immediately.
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 1000)),
new Promise(resolve => setTimeout(() => resolve(2), 2000)),
new Promise(resolve => setTimeout(() => resolve(3), 3000))
])
.then(results => JSON.stringify(results, null, 2))
.then(console.log);

// Results array
[
1,
2,
3
]

Promise.allSettled(promises) - Waits for All

  • Resolves with an array of objects detailing the outcome of each promise.
Promise.allSettled([
new Promise(resolve => setTimeout(() => resolve(1), 1000)),
new Promise((_, reject) => setTimeout(() => reject("Error!"), 2000)),
])
.then(results => JSON.stringify(results, null, 2))
.then(console.log)

// Results array
[
{
"status": "fulfilled",
"value": 1
},
{
"status": "rejected",
"reason": "Error!"
}
]

Promise.race(promises) - First to Settle

  • Resolves or rejects based on the first settled promise.
Promise.race([
new Promise(resolve => setTimeout(() => resolve(1), 1000)),
new Promise((_, reject) => setTimeout(() => reject("Whoops!"), 500))
]).then(console.log).catch(console.log);

// Simply call console. No Results array
Whoops!

Promise.any(promises) - First Success Wins

  • Resolves with the first successful promise.
  • If all fail, rejects with an AggregateError.
Promise.any([
new Promise((_, reject) => setTimeout(() => reject("Fail 1"), 1000)),
new Promise(resolve => setTimeout(() => resolve(2), 2000)),
new Promise(resolve => setTimeout(() => resolve(3), 3000))
]).then(console.log); // 2