Promises
Learn JavaScript promises as values for work that finishes later: pending, fulfilled, rejected, then, catch, finally, eager executors, and one-time settling.
- 01Name the three statesTell pending, fulfilled, and rejected apart, and know that a promise settles only once.
- 02Make a promiseUse
new Promisewithresolveandreject, and the shortcutsPromise.resolveandPromise.reject. - 03Add handlersUse
thenfor success,catchfor failure, andfinallyfor cleanup.
Promises are order tokens
Some work finishes later, like a timer or loading data. In the Callbacks lesson, you handled this by passing a function to the work and waiting for it to be called. A promise is another way. The work gives you an object right away. Later, that object gets the result. You add your instructions to the object.
A promise is a JavaScript object that stands for one result that comes later. It starts pending (still waiting). Then it becomes fulfilled with a value (success), or rejected with a reason (failure).
const coffee = Promise.resolve("tea");
coffee.then((drink) => console.log("Ready: " + drink));Line 1 makes a promise that is already fulfilled with the value "tea". Line 2 uses then to add a handler: a function that runs when the promise has its value. The handler gets "tea" as drink and prints "Ready: tea".
At a McDonald's counter, you order and pay. You get a token with a number on it right away. The kitchen makes your food while you wait. Later, your number shows on the screen and you take your food. Or the counter tells you the item is sold out.
- In real life: You pay and get an order token right away
- In JavaScript:
new Promise(...)gives you a promise right away - In real life: The kitchen is still making your food
- In JavaScript: The promise is pending
- In real life: Your number shows on the screen, and you take your food
- In JavaScript: The promise is fulfilled with a value
- In real life: The counter says your item is sold out
- In JavaScript: The promise is rejected with a reason
This lesson uses one promise at a time, with its handlers. The next lesson, Promise chaining, shows how to join several steps one after another.
Pending, fulfilled & rejected
INTERACTIVEA promise is always in one of three states:
- Pending: there is no result yet. Every promise starts here.
- Fulfilled: the work succeeded. The promise holds a value.
- Rejected: the work failed. The promise holds a reason, often an
Error.
Fulfilled and rejected share one name: settled. A settled promise has its final result.
const order = Promise.reject("sold out");
order.catch((reason) => {
console.log("Could not serve: " + reason);
});Line 1 makes a promise that is already rejected, with the reason "sold out". Line 3 adds a catch handler. catch runs when a promise is rejected, and it gets the reason. Line 4 puts the reason in a message, so the output is "Could not serve: sold out".
A rejected promise does not stop your script with an error on line 1. Rejected is just one of the promise's states. catch is the usual place to deal with the reason.
The table below has one row per state. Read each row as: "in this state, this is true, and this can happen next."
| State | What it means | What can happen next? |
|---|---|---|
| Pending | There is no result yet. You can add handlers while the work goes on. | It can become fulfilled or rejected. |
| Fulfilled | The work succeeded with a value, such as "order ready". | Nothing. It stays fulfilled forever. |
| Rejected | The work failed with a reason, such as "sold out" or an Error. | Nothing. It stays rejected forever. |
- A timer promise, before its timer fires
- A request for data that came back with the data
- A request promise whose network connection failed
resolve("ready")has already runreject("sold out")has already run- The executor started a timer, but the timer hasn't fired yet
Put each card under the state its promise is in right now.
The most important rule: a promise settles only once. After it is fulfilled or rejected, more calls to resolve or reject do nothing. (You'll meet these two functions in the next section.) Because the result never changes, every handler sees the same result, even a handler added late.
Creating promises
LIVE PROMISETo make your own promise, write new Promise(...) and pass it a function. This function is called the executor. JavaScript calls the executor right away and gives it two functions:
resolve(value): call it when the work succeeds. The promise becomes fulfilled withvalue.reject(reason): call it when the work fails. The promise becomes rejected withreason.
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.
There is no promise yet. When you press Order, new Promise gives back a promise right away, and the executor starts the timer.
The playground runs a real promise in your browser. Pick "Resolve, then try reject" and press Order. The log shows that the first call wins. The later reject call is ignored, because the promise is already settled.
Promise.resolve("already ready")
.then((value) => console.log(value));
Promise.reject("already failed")
.catch((reason) => console.log(reason));Promise.resolve(value) makes a promise that is already fulfilled with value. Promise.reject(reason) makes a promise that is already rejected with reason. These are shortcuts. They are handy in tests and small examples, and in functions that must return a promise even when the answer is known right away. This code prints already ready, then already failed.
const kitchen = new Promise((resolve) => {
setTimeout(() => resolve("pizza ready"), 5);
});
const order = new Promise((resolve) => resolve(kitchen));
order.then((value) => console.log(value));Lines 1 to 3 make a promise called kitchen. A timer fulfills it with "pizza ready" 5 milliseconds later. Line 5 makes a second promise, order, and calls resolve(kitchen). When you pass a promise to resolve, the outer promise waits for that inner promise and copies its result. So order does not get the kitchen object as its value. It gets "pizza ready", and line 6 prints pizza ready.
const order = new Promise(() => {
throw new Error("oven is off");
});
order.catch((error) => console.log(error.message));Line 2 throws an error inside the executor. JavaScript catches it and rejects the promise, with the error as the reason. On line 5, catch gets that error. It reads error.message and prints oven is off. So when your executor can fail, add a catch handler.
then, catch & finally
STEP THROUGHYou add handlers to a promise with three methods:
thenruns when the promise is fulfilled. It gets the value.catchruns when the promise is rejected. It gets the reason.finallyruns in both cases. It gets nothing. Use it to clean up, like hiding a loading spinner.
then runs on success and catch runs on failure. finally runs either way and gets no value.The table below compares the handlers. Read each row as: "this handler runs when this happens, and it gets this."
| Handler | When does it run? | What does it get? |
|---|---|---|
then(onFulfilled) | It runs when the promise is fulfilled. | It gets the value. |
then(onFulfilled, onRejected) | The first function runs on success, the second on failure. | The value on success, or the reason on failure. |
catch(onRejected) | It runs when the promise is rejected. | It gets the reason. |
finally(cleanup) | It runs in both cases. | It gets nothing. It is only for cleanup. |
finally is for cleanup. It gets no value, and the value passes on to the next handler.
script
.finally(() => { console.log("finally: wipe counter"); }) .then((value) => { console.log("then: " + value); });Step through the code above. The finally function does not get "noodles". It only prints its cleanup message. Then the value "noodles" passes on to the next then, which prints it. If a finally function throws an error, that error replaces the value. The lesson Error handling with promises covers that case.
A finally function gets no value and no reason. To read the value, use then. To read the reason, use catch. Use finally for work that must happen either way.
Promises run eagerly
ORDER PUZZLESMany people think then starts the promise's work. It does not. The executor runs during new Promise, right away. We say promises are eager: the work starts as soon as the promise is made.
A then function runs later. It waits until the current script has finished. Then it runs as a microtask: a small job that JavaScript runs as soon as the script is done. The Event loop and Microtasks in depth lessons explain this fully. Here, you only need the rule: normal lines first, promise handlers after.
Guess the print order first. The executor runs right away; the then function runs after the script ends.
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);});new Promise, so it prints before line 6. The then callback waits for the script to end, so it prints last.So adding a then handler does not start any work. The work started in the executor. The handler only says what to do with the result when it is ready.
The next example uses a promise that is already fulfilled. Its then handler still waits for the script to finish first.
Already fulfilled does not mean the handler runs now. Step through the script first, 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 different from an event like a click. If a click happens before you add a click listener, you miss that click. If a promise is fulfilled before you add then, the promise keeps the value. Your handler still runs and gets it.
const remembered = Promise.resolve("receipt #42");
setTimeout(() => {
remembered.then((value) => {
console.log("late handler saw " + value);
});
}, 10);Line 1 makes a promise that is already fulfilled with "receipt #42". Lines 3 to 7 wait 10 milliseconds, then add a then handler. The promise still has its value, so the handler prints late handler saw receipt #42.
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),
);Line 2 calls resolve("ready") first, so the promise is fulfilled with "ready". Line 3 tries to resolve again, and line 4 tries to reject. Both are ignored. Line 8 prints fulfilled: ready, and the function on line 9 never runs.
"ready". The calls on lines 3 and 4 do nothing.Because a promise keeps its one result, you can pass the promise to other code. That code can add a handler at any time, and the result will still be there.
Where you’ll use promises
Promises show up whenever JavaScript must wait without freezing the page. For example:
- waiting for a timer
- loading data from a server
- asking the user for permission, like using the camera
- reading files, in some environments such as Node.js
Later you'll learn async and await. They let promise code read from top to bottom like normal code, but they still use promises.
let loading = true;
loadMenu()
.then((menu) => {
renderMenu(menu);
})
.catch((error) => {
showError(error);
})
.finally(() => {
loading = false;
hideSpinner();
});This is a common pattern in real pages. Line 1 sets loading to true. Then:
- If
loadMenu()succeeds,thenshows the menu (lines 4 to 6). - If it fails,
catchshows an error (lines 7 to 9). - Either way,
finallysetsloadingtofalseand hides the spinner (lines 10 to 13).
function fakeFetch() {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ name: "Asha" }),
});
}
let loading = true;
fakeFetch()
.then((response) => response.json())
.then((user) => console.log("Hello " + user.name))
.catch(() => console.log("Could not load profile"))
.finally(() => {
loading = false;
console.log("loading: " + loading);
});The real fetch function loads data from a server and returns a promise. This example uses a pretend version, fakeFetch, so it runs without a network. It works like this:
fakeFetch()returns a promise for a response object.- The first
thencallsresponse.json(), which returns another promise, for the data{ name: "Asha" }. - The second
thengets that data and printsHello Asha. - If anything fails,
catchprintsCould not load profile. - Either way,
finallyprintsloading: false.
The real fetch rejects when the network fails, for example when you are offline. But if the server answers with an error page, like 404 (not found) or 500 (server error), the promise is still fulfilled. The response then has response.ok === false. So check response.ok before you read the data.
A delay(ms) function puts setTimeout inside a promise. Then other code can wait for the timer with then. You'll write it in exercise 2.
Common misconceptions
- “then starts the promise.” No. The executor already ran during
new Promise. - “If the promise is already fulfilled, the handler runs right now.” No. Promise handlers always wait until the current script finishes.
- “resolve gives back the promise's value.” No.
resolvesettles the promise. Handlers get the value later. - “A promise can be fulfilled and then rejected.” No. The first call to
resolveorrejectdecides the result forever. - “finally is where I read the value.” No.
finallygets nothing. Usethenorcatch. - “A rejected promise is always handled.” No. If you add no
catch, the browser or Node.js shows an "unhandled rejection" error. - “fetch rejects for every server error.” No. A 404 or 500 page still fulfills. Check
response.ok.
Practice exercises
5 EXERCISESDon't run it yet. 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
CLine 1 prints A. Making the promise runs the executor right away, so B prints next. The then function waits. Line 7 prints D. When the script is done, the then function prints C last.
setTimeout inside delay(ms)Write a timer function that returns a promise. You don't need reject here.
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 stays pending until the timer calls resolve. Then the then function runs and prints done.
Predict exactly what this catch 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 makes a rejected promise. catch gets the reason out of soup and prints handled: out of soup.
Predict the first line it prints. Then run it, and see that menu still reaches the then.
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 first and prints loading: false. Then the value menu passes on to the then, which prints it.
Which word prints? Why doesn't the other function 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") runs first and wins. The reject call after it is ignored, so the first function passed to then prints ready.
Check your understanding
9 QUESTIONSQuestion 1 of 9What state is a promise in while its work is not finished yet?
Choose an answer to see the explanation.
Question 2 of 9In what order does this code 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 9When does the executor of a
new Promiserun?Choose an answer to see the explanation.
Question 4 of 9What prints when resolve runs 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 9Which handler is for cleanup that must happen on success and on failure?
Choose an answer to see the explanation.
Question 6 of 9What is true about
finally((value) => ...)?Choose an answer to see the explanation.
Question 7 of 9Why can you add
thenafter a promise is already fulfilled?Choose an answer to see the explanation.
Question 8 of 9What does resolving
orderwith another promise do?Read the code, then predictconst kitchen = Promise.resolve("ready"); const order = new Promise((resolve) => resolve(kitchen)); order.then((value) => console.log(value));Choose an answer to see the explanation.
Question 9 of 9Which statement about the real
fetchis correct?Choose an answer to see the explanation.
Key takeaways
- A promise is an object for one result that comes later.
- It starts pending and settles once: fulfilled with a value, or rejected with a reason.
new Promiseruns its executor right away. Handlers run later, after the script finishes.thenis for success,catchis for failure, andfinallyis for cleanup.- A settled promise keeps its result, so a handler added late still gets it.
A promise is an object for work that finishes later: pending now, fulfilled or rejected later, and you can add handlers to it at any time.
Up next: Promise chaining.