completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Promises

Learn JavaScript promises as values for work that finishes later: pending, fulfilled, rejected, then, catch, finally, eager executors, and one-time settling.

By the end, you'll be able to
  • 01
    Name the three statesTell pending, fulfilled, and rejected apart, and know that a promise settles only once.
  • 02
    Make a promiseUse new Promise with resolve and reject, and the shortcuts Promise.resolve and Promise.reject.
  • 03
    Add handlersUse then for success, catch for failure, and finally for 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.

Promise, in one sentence

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

A promise that already holds teaPop out in the code editor (opens in a new tab)JavaScript
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".

Real-life analogyA promise is like an order token at a fast-food counter

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

INTERACTIVE

A 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.

A card that says pending, with two arrows. The top arrow leads to a card with the value order ready: fulfilled, with a value. The bottom arrow leads to a card with the reason sold out: rejected, with a reason.pendingstarts here"order ready"fulfilleda value"sold out"rejecteda reason
Every promise starts pending. Then it settles once: fulfilled with a value, or rejected with a reason.
A rejected promise holds a reasonPop out in the code editor (opens in a new tab)JavaScript
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."

The three promise states
StateWhat it meansWhat can happen next?
PendingThere is no result yet. You can add handlers while the work goes on.It can become fulfilled or rejected.
FulfilledThe work succeeded with a value, such as "order ready".Nothing. It stays fulfilled forever.
RejectedThe work failed with a reason, such as "sold out" or an Error.Nothing. It stays rejected forever.
Pending, fulfilled, or rejected?
  • 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 run
  • reject("sold out") has already run
  • The executor started a timer, but the timer hasn't fired yet
Try it yourself
0 of 6 correct

Put each card under the state its promise is in right now.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

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 PROMISE

To 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 with value.
  • reject(reason): call it when the work fails. The promise becomes rejected with reason.
Order playground: settle a real promise
The order promisePop out in the code editor (opens in a new tab)JavaScript
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"));
Runtimeidle
resultNo promise yet
delay700 ms
outcomefulfill
What the handlers printed
  1. Choose an outcome, then press Order.
Try it yourself

There is no promise yet. When you press Order, new Promise gives back a promise right away, and the executor starts the timer.

This runs a real Promise with a real setTimeout. Pick a delay and an outcome, then press Order. Reset stops 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.

Promises that are settled from the startPop out in the code editor (opens in a new tab)JavaScript
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.

Resolving with another promisePop out in the code editor (opens in a new tab)JavaScript
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.

An error in the executor rejects the promisePop out in the code editor (opens in a new tab)JavaScript
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 THROUGH

You add handlers to a promise with three methods:

  • then runs when the promise is fulfilled. It gets the value.
  • catch runs when the promise is rejected. It gets the reason.
  • finally runs in both cases. It gets nothing. Use it to clean up, like hiding a loading spinner.
A promise card with three arrows. The first leads to then, which runs on success and gets the value. The second leads to catch, which runs on failure and gets the reason. The third leads to finally, which runs either way and gets nothing.promisethensuccess: gets the valuecatchfailure: gets the reasonfinallyeither way: gets nothing
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."

Which handler to use
HandlerWhen 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 cleans up and passes the value on
Step 0 of 5Ready
Your turn: follow the blue line

finally is for cleanup. It gets no value, and the value passes on to the next handler.

Running in
  1. script
Next: line 1
  .finally(() => {    console.log("finally: wipe counter");  })  .then((value) => {    console.log("then: " + value);  });
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

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.

What finally gets

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 PUZZLES

Many 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.

The executor runs now, the then function later
Step 0 of 8Ready
Your turn: follow the blue line

Guess the print order first. The executor runs right away; the then function runs after the script ends.

Running in
  1. script
Next: line 1
const order = new Promise((resolve) => {  console.log("executor: kitchen starts");  resolve("tacos");});console.log("after new Promise");order.then((value) => {  console.log("then: " + value);});
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
The printed order, left to right. 1: before new Promise, from line 1, now. 2: executor: kitchen starts, from line 3, now. 3: after new Promise, from line 6, now. 4: then: tacos, from line 8, later, as a microtask.the script runs these nowbeforeexecutoraftera microtaskthen: tacosline 1: nowline 3: nowline 6: nowline 8: later
The executor runs inside 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, but the handler still waits
Step 0 of 5Ready
Your turn: follow the blue line

Already fulfilled does not mean the handler runs now. Step through the script first, then the microtask.

Running in
  1. script
Next: line 1
console.log("A: cup ordered");ready.then((value) => {  console.log("B: then gets " + value);});console.log("C: keep walking");
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

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.

A handler added late still gets the valuePop out in the code editor (opens in a new tab)JavaScript
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.

A promise cannot settle twicePop out in the code editor (opens in a new tab)JavaScript
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.

Three calls in order. resolve ready is first and wins. resolve extra ready and reject out of buns are ignored. An arrow from the first call leads to the order cart, which holds fulfilled and ready.resolve("ready")1st: winsresolve("extra ready")ignoredreject("out of buns")ignoredfulfilled"ready"order
Line 2 settles the promise first, so it stays fulfilled with "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.

Turn off loading when the menu is donePop out in the code editor (opens in a new tab)JavaScript
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, then shows the menu (lines 4 to 6).
  • If it fails, catch shows an error (lines 7 to 9).
  • Either way, finally sets loading to false and hides the spinner (lines 10 to 13).
Load a profile with a pretend fetchPop out in the code editor (opens in a new tab)JavaScript
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:

  1. fakeFetch() returns a promise for a response object.
  2. The first then calls response.json(), which returns another promise, for the data { name: "Asha" }.
  3. The second then gets that data and prints Hello Asha.
  4. If anything fails, catch prints Could not load profile.
  5. Either way, finally prints loading: false.
fetch still fulfills for a 404 page

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 small helper you'll write often

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. resolve settles the promise. Handlers get the value later.
  • “A promise can be fulfilled and then rejected.” No. The first call to resolve or reject decides the result forever.
  • “finally is where I read the value.” No. finally gets nothing. Use then or catch.
  • “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 EXERCISES
Exercise 1 · Warm-upPredict the print order

Don't run it yet. Write the four letters in the order they print.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log("A");
const order = new Promise((resolve) => {
  console.log("B");
  resolve("C");
});
order.then((value) => console.log(value));
console.log("D");

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticePut setTimeout inside delay(ms)

    Write a timer function that returns a promise. You don't need reject here.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function delay(ms) {
      // return a promise that fulfills after ms
    }
    
    delay(500).then(() => console.log("done"));
      Exercise 3 · PracticeHandle a rejection with catch

      Predict exactly what this catch handler prints.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      Promise.reject("out of soup")
        .catch((reason) => console.log("handled: " + reason));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeAdd finally for cleanup

        Predict the first line it prints. Then run it, and see that menu still reaches the then.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        let loading = true;
        Promise.resolve("menu")
          .finally(() => {
            loading = false;
            console.log("loading: " + loading);
          })
          .then((value) => console.log(value));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeExplain why a promise cannot settle twice

          Which word prints? Why doesn't the other function run?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const order = new Promise((resolve, reject) => {
            resolve("ready");
            reject("sold out");
          });
          order.then(
            (value) => console.log(value),
            (reason) => console.log(reason),
          );

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            9 QUESTIONS
            Lesson quiz · 9 questionsScore: first tries count
            1. Question 1 of 9What state is a promise in while its work is not finished yet?

              Choose an answer to see the explanation.

            2. Question 2 of 9In what order does this code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log("one");
              Promise.resolve("two").then((value) => console.log(value));
              console.log("three");

              Choose an answer to see the explanation.

            3. Question 3 of 9When does the executor of a new Promise run?

              Choose an answer to see the explanation.

            4. Question 4 of 9What prints when resolve runs before reject?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const 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.

            5. Question 5 of 9Which handler is for cleanup that must happen on success and on failure?

              Choose an answer to see the explanation.

            6. Question 6 of 9What is true about finally((value) => ...)?

              Choose an answer to see the explanation.

            7. Question 7 of 9Why can you add then after a promise is already fulfilled?

              Choose an answer to see the explanation.

            8. Question 8 of 9What does resolving order with another promise do?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const kitchen = Promise.resolve("ready");
              const order = new Promise((resolve) => resolve(kitchen));
              order.then((value) => console.log(value));

              Choose an answer to see the explanation.

            9. Question 9 of 9Which statement about the real fetch is 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 Promise runs its executor right away. Handlers run later, after the script finishes.
            • then is for success, catch is for failure, and finally is 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.

            CompleteFrontend Clear concepts. Working examples.