By WebNest Studio Engineering Team
JavaScript Tutorial
JavaScript Promises
A Promise is an object representing the eventual result of an asynchronous operation — something that hasn't finished yet, like a network request, a file read, or a timer. Before Promises, asynchronous code relied on nested callbacks, which became difficult to read and error-prone as operations chained together ("callback hell").
A Promise gives asynchronous code a predictable shape: it starts in one state and settles into exactly one of two final states, and you attach handlers that run once that settlement happens.
Why it matters
Anything that takes time in JavaScript, such as a network request, a timer or reading a file, gives back a promise: an object standing for a result that is not ready yet. Promises replaced deeply nested callbacks, and async and await are built directly on top of them, so they cannot be skipped.
The Three States
Every Promise is in exactly one of three states at any moment.
- Pending — the initial state; the operation hasn't completed yet.
- Fulfilled — the operation completed successfully, producing a value.
- Rejected — the operation failed, producing a reason (usually an Error).
.then, .catch, and .finally
.then(onFulfilled, onRejected) registers callbacks for success and (optionally) failure. .catch(onRejected) is shorthand for .then(undefined, onRejected) and is the conventional way to handle errors at the end of a chain. .finally(callback) runs regardless of whether the Promise fulfilled or rejected, useful for cleanup like hiding a loading spinner. Because .then() always returns a new Promise, these calls chain together, and each .then() receives the value returned by the previous one.
Combining Promises with Promise.all
Promise.all(iterable) takes an array of Promises and returns a single Promise that fulfills with an array of all their results, but only once every input Promise has fulfilled — and it rejects immediately if any single one rejects. This is the standard tool for running independent async operations in parallel rather than awaiting them one at a time. Related methods include Promise.allSettled() (waits for all, never rejects, reports each outcome) and Promise.race() (settles as soon as the first input settles).
Examples
A basic Promise chain with .then, .catch, and .finally
function fetchUserAge(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id === 1) {
resolve(28);
} else {
reject(new Error('User not found'));
}
}, 100);
});
}
fetchUserAge(1)
.then((age) => {
console.log('Age is', age);
return age * 2;
})
.then((doubled) => console.log('Doubled:', doubled))
.catch((err) => console.error('Failed:', err.message))
.finally(() => console.log('Request finished'));
Age is 28
Doubled: 56
Request finished
Running independent async operations in parallel with Promise.all
const delay = (value, ms) =>
new Promise((resolve) => setTimeout(() => resolve(value), ms));
Promise.all([delay('users', 50), delay('posts', 30), delay('comments', 10)])
.then((results) => console.log(results))
.catch((err) => console.error(err));
[ 'users', 'posts', 'comments' ]
Common Mistakes
- Forgetting to add a
.catch()at the end of a Promise chain, which results in silent or unhandled promise rejections. - Nesting
.then()calls inside each other instead of returning a value and chaining flatly — this recreates the "callback hell" Promises were meant to fix. - Using
Promise.all()when one failure shouldn't cancel the rest —Promise.allSettled()is the correct choice when you want every result regardless of individual failures. - Forgetting that
.then()'s callback must return a value (or another Promise) to pass data down the chain — an implicitundefinedreturn breaks the chain's data flow.
Exercise
Write delay, which returns a promise that resolves with the given value after the given number of milliseconds. The code below then handles a rejected promise with catch and finally, and waits for two delays together with Promise.all.
Expected output: boom, done, ab (one per line)
function delay(ms, value) {
// TODO: return a promise that resolves with value after ms milliseconds
}
Promise.reject(new Error("boom"))
.catch((error) => console.log(error.message))
.finally(() => console.log("done"));
Promise.all([delay(20, "a"), delay(10, "b")])
.then((values) => console.log(values.join("")));
Show hints
- A promise is created with
new Promise((resolve) => { ... }). - Inside it,
setTimeout(() => resolve(value), ms)resolves the promise after the delay.
Show solution
function delay(ms, value) {
return new Promise((resolve) => {
setTimeout(() => resolve(value), ms);
});
}
Promise.reject(new Error("boom"))
.catch((error) => console.log(error.message))
.finally(() => console.log("done"));
Promise.all([delay(20, "a"), delay(10, "b")])
.then((values) => console.log(values.join("")));
Quick quiz
1. What are the three states of a promise?
- A. Open, closed, failed
- B. Waiting, active, complete
- C. Start, running, done
- D. Pending, fulfilled, rejected
Show answer
D. A promise starts pending and settles once, as either fulfilled or rejected.
2. In what order does Promise.all return its results?
- A. The order of the promises in the input array
- B. Random order
- C. The order in which the promises finished
- D. Alphabetical order
Show answer
A. The order of the array is kept, whichever promise settles first.
3. What happens to Promise.all if one of its promises rejects?
- A. It ignores that promise
- B. It rejects immediately with that error
- C. It waits for the others and returns partial results
- D. It retries the failed promise
Show answer
B. Use Promise.allSettled to wait for every promise and get each outcome.
Interview questions
What is a promise and what problem does it solve?
Show answer
A promise is an object representing the eventual result of an asynchronous operation. It is pending at first and later becomes fulfilled with a value or rejected with an error. Handlers are attached with then, catch and finally, and because each returns a new promise they can be chained. Promises replaced nested callbacks, known as callback hell, with a flat chain and a single place to handle errors.
What is the difference between Promise.all, allSettled, race and any?
Show answer
Promise.all fulfils with an array of all the values, or rejects as soon as one promise rejects. Promise.allSettled always waits for every promise and reports the outcome of each. Promise.race settles as soon as the first promise settles, whether it fulfils or rejects. Promise.any fulfils with the first promise to fulfil and rejects only if all of them reject.
Key Points to Remember
- A Promise is pending, then settles exactly once into either fulfilled or rejected.
.then()handles success,.catch()handles failure, and.finally()always runs for cleanup.- Promise chains work because
.then()returns a new Promise, letting values flow down the chain. Promise.all()runs Promises in parallel and rejects fast;Promise.allSettled()waits for all outcomes without short-circuiting.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Shows a live preview of your HTML, CSS and JavaScript. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering