Modern JS & AsyncExtra· 45 min read

Promises in Depth: then, catch & Promise.all

Behind every async/await is a Promise. Learn to create them, chain .then/.catch, and run many at once — the real machinery of data-driven apps.

What you will learn

  • Create a Promise and chain .then / .catch
  • See how async/await maps onto Promises
  • Run many Promises together with Promise.all

A Promise is a ticket for a future value

A Promise is an object that stands for a value you do not have yet — like a restaurant buzzer that will eventually light up. It lives in one of three states: pending (still waiting), fulfilled (the value arrived — we say it resolved), or rejected (something went wrong). You already used Promises through await; now we look at them directly.

You build one with new Promise(...). It hands you two functions: call resolve(value) when things succeed, or reject(error) when they fail. The example fakes a slow task that succeeds after a moment.

Create a Promise and read it with .then
<p id="out">Working...</p>
<script>
  function loadUser() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ name: "Asha", role: "Student" });   // success
        // reject("User not found");                  // failure path
      }, 800);
    });
  }

  loadUser().then(user => {
    document.getElementById("out").textContent = "Loaded: " + user.name;
  });
</script>
Live preview

Step by step:

  1. loadUser() returns a new Promise that starts pending — no value yet.
  2. Inside, setTimeout waits 800ms to simulate a slow server.
  3. When the time is up, resolve({ name: "Asha", ... }) fulfils the Promise with that object.
  4. .then(user => ...) is the "when it resolves, do this" handler — it receives the resolved value as user.
  5. We show user.name, so "Working..." becomes "Loaded: Asha".

Note: Output: At first: Working... After ~0.8s: Loaded: Asha

.then chains and .catch handles errors

The power of .then is that it chains: each .then returns a new Promise, so you can line up steps that depend on the previous one. A single .catch at the end catches a failure from any step in the chain.

Each .then transforms the value and passes it on
<p id="r"></p>
<script>
  function getNumber() {
    return new Promise(resolve => resolve(5));
  }

  getNumber()
    .then(n => n * 2)          // 5 → 10  (return passes to the next .then)
    .then(n => n + 1)          // 10 → 11
    .then(final => {
      document.getElementById("r").textContent = "Result: " + final;
    })
    .catch(err => {
      document.getElementById("r").textContent = "Failed: " + err;
    });
</script>
Live preview

The value flows down the chain: getNumber() resolves with 5; the first .then returns 5 × 2 = 10, which becomes the input of the next .then (10 + 1 = 11), which the last .then displays. If any step threw an error, the chain would jump straight to .catch. The key habit: return a value from a .then so the next one receives it.

Note: Output: Result: 11

Tip: async/await is just a friendlier face on this. await loadUser() does the same job as loadUser().then(...) — and a try/catch around it does the same job as .catch. Knowing both means you can read any codebase.

Promise.all — run many at once

Often you need several independent results — say three API calls. Doing them one after another (await A, then await B, then await C) is slow, because each waits for the last. Promise.all runs them at the same time and waits for all to finish, which is far faster.

Promise.all runs Promises in parallel
<p id="all">Loading three things...</p>
<script>
  const wait = (label, ms) =>
    new Promise(resolve => setTimeout(() => resolve(label), ms));

  // Start all three together; wait for all to finish
  Promise.all([
    wait("A", 600),
    wait("B", 400),
    wait("C", 500)
  ]).then(results => {
    document.getElementById("all").textContent = "All done: " + results.join(", ");
  });
</script>
Live preview

Promise.all([...]) takes an array of Promises and starts them together. Because they overlap, the total wait is about the slowest one (600ms), not the sum (1500ms). When every Promise has resolved, .then runs once with an array of all the results, in the same order you listed them — so results is ["A", "B", "C"].

Note: Output: At first: Loading three things... After ~0.6s: All done: A, B, C

Watch out: Promise.all fails fast: if any one Promise rejects, the whole thing rejects immediately. When you instead want every result regardless of failures, use Promise.allSettled, which always waits for all and reports each as fulfilled or rejected.

The combinators at a glance

MethodResolves whenUse for
Promise.all([...])ALL succeed (fails if any rejects)Need every result, e.g. load a dashboard’s data
Promise.allSettled([...])ALL finish (success or failure)Want every outcome even if some fail
Promise.race([...])The FIRST one settlesTimeouts, "whichever is fastest wins"
Promise.any([...])The first SUCCESSTry several sources, take the first that works

Q. What is the main advantage of Promise.all over awaiting three Promises one by one?

Answer: Promise.all starts all the Promises together (in parallel), so total time is roughly the slowest one rather than the sum of all of them.

✍️ Practice

  1. Create a Promise that resolves with a value after 1 second and read it with .then.
  2. Build a 3-step .then chain that transforms a number, and add a .catch.
  3. Use Promise.all to wait for two fake "fetches" and combine their results.

🏠 Homework

  1. Rewrite a small async/await function using .then/.catch instead, then convert it back — and note which you find clearer.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →