Code of the Day
IntermediateThe runtime

Promises in depth

Compose asynchronous work — chaining, parallelism, and error handling.

JavaScript / TSIntermediate9 min read
Recommended first
By the end of this lesson you will be able to:
  • Explain a promise's three states
  • Run asynchronous work in parallel with Promise.all
  • Handle async errors with try/catch around await

The async lesson introduced promises and async/await. This one goes deeper: the states a moves through, how to run async work in parallel, and how errors propagate. These are the tools for real asynchronous code.

Three states

A promise is always in one of three states:

  • pending — the work isn't finished yet.
  • fulfilled — it completed with a value.
  • rejected — it failed with an error.

It settles once (to fulfilled or rejected) and stays there. await gives you the fulfilled value, or throws the rejection.

Sequential vs parallel

Awaiting one thing after another is sequential — fine when each step depends on the last, wasteful when they're independent:

// Sequential: ~2x as slow if the calls don't depend on each other
const a = await getUser(1);
const b = await getUser(2);

Promise.all starts them together and waits for all to finish:

// Parallel: both run at once
const [a, b] = await Promise.all([getUser(1), getUser(2)]);

This is the concurrency fundamentals lesson in practice: independent work should overlap. Related helpers: Promise.allSettled (wait for all, successes and failures) and Promise.race (settle as soon as the first one does).

Handling errors

A rejected promise that you await throws — so you catch it with ordinary try/catch:

async function load() {
  try {
    const [a, b] = await Promise.all([getUser(1), getUser(2)]);
    return { a, b };
  } catch (err) {
    console.error("one of the requests failed:", err);
    return null;
  }
}

Note that with Promise.all, if any promise rejects, the whole thing rejects — so a single try/catch covers them all.

An unhandled promise rejection won't crash the line that created it — it surfaces later as an "unhandled rejection." Always await inside a try/catch, or attach a .catch(...), so failures are handled where you expect.

Where to go next

Next: classes and prototypes — JavaScript's model for objects with shared behaviour.

Finished reading? Mark it complete to track your progress.

On this page