⚡IntermediateAsync JavaScript

Promises

Represent future values with Promises chaining .then(), handling errors with .catch(), and running operations in parallel.

Overview

A Promise is an object that represents the eventual result of an asynchronous operation. When you call a function that returns a Promise, you get back a placeholder immediately. You then attach callbacks with .then() and .catch() to handle the result when it arrives without nesting. A Promise is always in one of three states: pending (the operation is still in progress), fulfilled (it completed successfully with a value), or rejected (it failed with a reason). Once settled (fulfilled or rejected), a Promise's state never changes. Promises were introduced in ES6 to address callback hell. They allow you to chain asynchronous steps in a flat, readable sequence, and provide a single .catch() at the end to handle any error that occurred anywhere in the chain. The modern fetch API, many browser APIs, and most Node.js libraries return Promises.

Key Points

  • •A Promise has three states: pending, fulfilled, or rejected and transitions only once
  • •.then(onFulfilled) registers a callback to run when the Promise resolves successfully
  • •.catch(onRejected) registers a callback for rejection equivalent to .then(null, onRejected)
  • •.finally(fn) runs whether the Promise fulfilled or rejected great for cleanup
  • •Chaining: each .then() returns a new Promise, allowing flat sequential async steps
  • •Promise.all([p1, p2]) resolves when all resolve; rejects as soon as any rejects
  • •Promise.allSettled([p1, p2]) waits for all to settle regardless of outcome
  • •Promise.race([p1, p2]) resolves/rejects with the first Promise to settle

Examples

Creating and consuming a Promise

function fetchUser(id) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      if (id <= 0) {
        reject(new Error("Invalid user ID"));
        return;
      }
      resolve({ id: id, name: "Alice" });
    }, 300);
  });
}

fetchUser(1)
  .then(function(user) {
    console.log("User:", user.name); // User: Alice
    return user.name.toUpperCase();
  })
  .then(function(upper) {
    console.log("Upper:", upper);   // Upper: ALICE
  })
  .catch(function(err) {
    console.error("Error:", err.message);
  });

Each .then() receives the return value of the previous .then(). Returning a value wraps it in a resolved Promise automatically.

Promise chaining for sequential async steps

function getUser(id) {
  return Promise.resolve({ id, name: "Bob", teamId: 42 });
}

function getTeam(teamId) {
  return Promise.resolve({ id: teamId, name: "Engineering" });
}

getUser(1)
  .then(user => {
    console.log("User:", user.name);
    return getTeam(user.teamId);   // return a Promise chain waits for it
  })
  .then(team => {
    console.log("Team:", team.name);
  })
  .catch(err => console.error(err));

// Output:
// User: Bob
// Team: Engineering

When you return a Promise from .then(), the chain waits for that Promise to resolve before calling the next .then().

Running Promises in parallel with Promise.all

const p1 = Promise.resolve("Result A");
const p2 = new Promise(resolve => setTimeout(() => resolve("Result B"), 200));
const p3 = fetch ? fetch("https://jsonplaceholder.typicode.com/todos/1").then(r => r.json()) : Promise.resolve({ title: "Sample" });

Promise.all([p1, p2])
  .then(([a, b]) => {
    console.log(a); // Result A
    console.log(b); // Result B
  });

// Promise.allSettled doesn't short-circuit on rejection
const mixed = [
  Promise.resolve("ok"),
  Promise.reject(new Error("oops")),
  Promise.resolve("also ok"),
];

Promise.allSettled(mixed).then(results => {
  results.forEach(r => console.log(r.status, r.value ?? r.reason?.message));
});

Promise.all is ideal when operations are independent and you need all results. Promise.allSettled is better when you want every result regardless of failures.

Common Mistakes

  • Forgetting to return a Promise inside .then() without return, the chain does not wait for the inner async operation
  • Not having a .catch() unhandled rejections cause warnings in Node.js and can crash apps
  • Nesting .then() inside .then() instead of chaining recreates callback hell with Promises
  • Confusing Promise.all (fails fast) with Promise.allSettled (waits for all)

Note

When a .then() callback returns a Promise, the chain automatically unwraps it you never get a Promise-inside-a-Promise. This "flattening" is what makes chaining work cleanly.

Exercises

Write JavaScript code to solve each exercise. Run it and check your output.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

What are the three possible states of a Promise?