Promises
Learn JavaScript promises as values for work that finishes later: pending, fulfilled, rejected, then, catch, finally, eager executors, and one-time settling.
- 01Read the three statesTell pending, fulfilled, and rejected promises apart and know that settling happens once.
- 02Create a promiseUse new Promise with resolve and reject, plus Promise.resolve and Promise.reject shortcuts.
- 03Attach handlersUse then, catch, and finally for success, failure, and cleanup without jumping ahead to chaining.
Promises are order tokens
The Callbacks lesson introduced a problem: when work finishes later, we often pass a function and hope someone calls it correctly. A promise gives that future result a stable object. Instead of handing away all control, your code receives a value now and attaches instructions to it.
A promise is a JavaScript object representing one future result: it starts pending, then becomes either fulfilled with a value or rejected with a reason.
You order and pay at the McDonald's counter, then get a token right away. While the kitchen prepares your food, you can wait; when the number appears, you collect it or hear that an item is sold out.
- In real life: Pay and receive an order token
- In JavaScript:
new Promise(...)returns a promise immediately - In real life: The kitchen prepares your food
- In JavaScript: The promise is pending
- In real life: Your number appears; collect food
- In JavaScript: The promise fulfills with a value
- In real life: The counter says an item is sold out
- In JavaScript: The promise rejects with a reason
Where the analogy stops: A real token can be reused for the next customer; a JavaScript promise settles once and stays settled.
This lesson stays focused on a single promise and a single set of handlers. The next lesson, Promise chaining, shows how returned values and returned promises connect multiple steps.
Pending, fulfilled & rejected
INTERACTIVEA promise is always in exactly one state. It begins pending. From pending, it may fulfill with a value, or reject with a reason. The shared word for fulfilled and rejected is settled.
| State | Meaning | What can happen next |
|---|---|---|
| Pending | No final result yet. Handlers can be attached while the work continues. | It may become fulfilled or rejected. |
| Fulfilled | The operation succeeded with a value, such as "order ready". | It stays fulfilled forever. |
| Rejected | The operation failed with a reason, such as "sold out" or an Error. | It stays rejected forever. |
A tracking page is useful even before the package arrives because it names the not-yet state. Promises are useful for the same reason: your program can pass around “not ready yet” without losing track of what should happen later.
- In real life: In transit
- In JavaScript: Pending
- In real life: Delivered
- In JavaScript: Fulfilled with the package
- In real life: Delivery failed
- In JavaScript: Rejected with a reason
Where the analogy stops: A shipping company can update many intermediate statuses. A promise exposes only pending, fulfilled, or rejected.
- A timeout promise before its timer fires
- A data request that has returned JSON successfully
- A request promise whose network connection failed
resolve("ready")has already runreject("sold out")has already run- The executor has started a timer but the delay has not ended
Sort each scenario by the promise state it describes.
The most important state rule is one-way movement. Once a promise is fulfilled or rejected, extra calls to resolve or reject do nothing. That one-time behavior is what makes late handlers safe: they all observe the same remembered result.
Creating promises
LIVE PROMISEYou create a custom promise with new Promise. The constructor receives an executor function. JavaScript calls that executor with two functions: resolve for success and reject for failure.
const order = new Promise((resolve, reject) => { setTimeout(() => { if (outcome === "fulfill") resolve("order ready"); else reject("sold out"); }, delay);}); order .then((value) => log("then: " + value)) .catch((reason) => log("catch: " + reason)) .finally(() => log("finally: throw away the receipt"));- Choose an outcome, then press Order.
No promise has been created yet. When you press Order, new Promise returns immediately and the executor starts the timer.
The playground runs real promises in the browser. Choose “Resolve, then try reject” and notice the log: the first result wins. The later attempt is ignored because the promise has already settled.
Promise.resolve("already ready")
.then((value) => console.log(value));
Promise.reject("already failed")
.catch((reason) => console.log(reason));Promise.resolve(value) and Promise.reject(reason) are shortcuts for promises that are already settled. They are useful in tests, examples, and code that wants to return a promise-shaped result even when the answer is known immediately.
then, catch & finally
STEP THROUGHHandlers are instructions attached to the order promise. then handles fulfillment, catch handles rejection, and finally handles cleanup either way.
| Handler | Runs when | Receives |
|---|---|---|
then(onFulfilled) | The promise fulfills | The fulfillment value |
then(onFulfilled, onRejected) | Fulfillment or rejection at this exact point | Value for success, reason for failure |
catch(onRejected) | The promise rejects | The rejection reason |
finally(cleanup) | Either fulfillment or rejection | No value or reason; it is for cleanup |
finally is for cleanup. It does not receive the value, and a normal finally callback lets that value continue.
script
.finally(() => { console.log("finally: wipe counter"); }) .then((value) => { console.log("then: " + value); });The finally callback above does not receive "noodles". It simply logs cleanup and lets the original value continue to the next then. If a finally callback throws, that new error takes over; the Error handling with promises lesson focuses on those paths.
Promises run eagerly
ORDER PUZZLESA common misconception is that then starts a promise. It does not. The executor starts during new Promise. The callback passed to then runs later, as a microtask, after the current script finishes. The Event loop and Microtasks in depth lessons build the full scheduling model; this lesson uses the rule.
Predict the printed order. The executor is synchronous; the then callback is asynchronous microtask work.
script
const order = new Promise((resolve) => { console.log("executor: kitchen starts"); resolve("tacos");});console.log("after new Promise");order.then((value) => { console.log("then: " + value);});The token analogy is exact here: the kitchen starts cooking when you order, not when you look at your token. Attaching handlers is like deciding what to do when your number shows on the screen.
Already fulfilled does not mean callback now. Step through the script, then the microtask.
script
console.log("A: cup ordered");ready.then((value) => { console.log("B: then gets " + value);});console.log("C: keep walking");Settled promises remember
A promise is not an event. If a click happens before you add a click listener, that old click is gone. If a promise fulfills before you add then, the promise remembers the value and still calls your handler later.
const remembered = Promise.resolve("receipt #42");
setTimeout(() => {
remembered.then((value) => {
console.log("late handler saw " + value);
});
}, 10);const order = new Promise((resolve, reject) => {
resolve("ready");
resolve("extra ready");
reject("out of buns");
});
order.then(
(value) => console.log("fulfilled: " + value),
(reason) => console.log("rejected: " + reason),
);That memory is one reason promises are easier to compose than plain callbacks. You can pass the promise object around without worrying that the result will disappear before a caller attaches a handler.
Where you’ll use promises
Promises appear whenever JavaScript needs to wait without freezing the page: timers, loading data, asking a user for permission, reading files in some environments, and many browser APIs. Later, async and await make promise code read more like normal top-to-bottom code, but promises are still underneath.
let loading = true;
loadMenu()
.then((menu) => {
renderMenu(menu);
})
.catch((error) => {
showError(error);
})
.finally(() => {
loading = false;
hideSpinner();
});In real interfaces, finally is often where a loading flag turns off. The success path renders data. The failure path shows an error. Cleanup runs for both.
A delay(ms) helper wraps setTimeout in a promise so other code can wait for a timer with promise handlers.
Common misconceptions
- “then starts the promise.” No. The executor already ran during
new Promise. - “Already fulfilled means the callback runs right now.” No. Promise handlers run later as microtasks.
- “resolve returns the value from the promise.” No.
resolvesettles the promise; handlers receive the value later. - “A promise can be both fulfilled and rejected.” No. The first settle call wins forever.
- “finally is where I read the value.” No.
finallyreceives no value; usethenorcatch.
Practice exercises
5 EXERCISESWithout running it first, write the four letters in the order they print.
console.log("A");
const order = new Promise((resolve) => {
console.log("B");
resolve("C");
});
order.then((value) => console.log(value));
console.log("D");A
B
D
CA logs first. Creating the promise runs the executor, so B logs next. The then callback is queued, line D logs, and the queued callback prints C last.
setTimeout in delay(ms)Create a promise-based timer helper. You do not need to reject for this exercise.
function delay(ms) {
// return a promise that fulfills after ms
}
delay(500).then(() => console.log("done"));function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}The promise is pending until the timer calls resolve. The then handler runs after that.
Predict exactly what this rejection handler prints.
Promise.reject("out of soup")
.catch((reason) => console.log("handled: " + reason));Promise.reject("out of soup")
.catch((reason) => console.log("handled: " + reason));Promise.reject creates a rejected promise. catch receives out of soup and logs the handled message.
Predict the first log line. Then run it and notice that menu still passes through.
let loading = true;
Promise.resolve("menu")
.finally(() => {
loading = false;
console.log("loading: " + loading);
})
.then((value) => console.log(value));let loading = true;
Promise.resolve("menu")
.finally(() => {
loading = false;
console.log("loading: " + loading);
})
.then((value) => console.log(value));The promise is fulfilled with menu. finally runs cleanup first and logs loading: false; then the value passes through to the next then.
Which word prints, and why does the other branch not run?
const order = new Promise((resolve, reject) => {
resolve("ready");
reject("sold out");
});
order.then(
(value) => console.log(value),
(reason) => console.log(reason),
);const order = new Promise((resolve, reject) => {
resolve("ready");
reject("sold out");
});
order.then(
(value) => console.log(value),
(reason) => console.log(reason),
);resolve("ready") wins. The later reject call is ignored, so the fulfillment handler prints ready.
Check your understanding
7 QUESTIONSQuestion 1 of 7What is a promise while its async work is still unfinished?
Choose an answer to see the explanation.
Question 2 of 7What order does the already-fulfilled promise print?
Read the code, then predictconsole.log("one"); Promise.resolve("two").then((value) => console.log(value)); console.log("three");Choose an answer to see the explanation.
Question 3 of 7When does a Promise executor run?
Choose an answer to see the explanation.
Question 4 of 7What prints when resolve wins before reject?
Read the code, then predictconst p = new Promise((resolve, reject) => { resolve("first"); reject("second"); }); p.then( (value) => console.log(value), (reason) => console.log(reason), );Choose an answer to see the explanation.
Question 5 of 7Which handler is meant for cleanup that should happen on success or failure?
Choose an answer to see the explanation.
Question 6 of 7What is true about
finally((value) => ...)?Choose an answer to see the explanation.
Question 7 of 7Why can you attach
thenafter a promise has already fulfilled?Choose an answer to see the explanation.
Key takeaways
- A promise is an object for one future result.
- It starts pending and settles once: fulfilled with a value or rejected with a reason.
new Promiseruns its executor immediately; handlers run later as microtasks.then,catch, andfinallydescribe success, failure, and cleanup.- Settled promises remember their result, so handlers attached late still work.
A promise is JavaScript’s one-time order token for async work: pending now, fulfilled or rejected later, and safe to attach handlers to at any time.
Up next: Promise chaining.