Skip to main content

JS - Promises Basics

· 7 min read

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

A promise 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); // Success!
return "Continuing execution";
})
.then(msg => {
console.log(msg); // Continuing execution
})
.catch(error => {
console.error("Error caught:", error); // Never executes
});

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

If an error occurs at any stage of a promise chain, the first .catch() after the error will 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: fail...");
throw new Error("Step 3 Failure");
})
.catch(error => {
console.error("Caught an error:", error.message); // error.message = "Step 3 Failure"

console.log("Handling error, but continuing...");
return "Error handled. Resuming";
})
.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. Resuming

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:", 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")
});

Promise Comparison

  • Promise.all (Resolves when all succeed, but fails immediately if any reject)
  • Promise.race (First to settle wins) – Resolves or rejects with the first settled promise
  • 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
MethodResolves WhenRejects WhenResult FormatBest Used For
all()All promises complete successfullyIf any promise failsArray of resolved valuesWhen every task must succeed
allSettled()After all promises finish, regardless of success or failureNever rejectsArray of { status, value/reason }When you need results of all tasks, even failed ones
any()As soon as one promise succeedsIf all promises failThe first successful valueWhen only one success is needed
race()As soon as any promise finishes, success or failureSame as resolvesValue or error from first finished promiseWhen you care about first result, no matter what
resolve()Immediately resolves with given valueNever rejectsThe provided value (or promise result)Converting non-promises to promises
reject()Never resolvesImmediately rejects with reasonThe rejection reasonSimulating or forcing an error
then()When the original promise resolvesWhen the original promise rejectsWhatever the .then() handler returnsChaining async operations
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

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);

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