Skip to main content

What is a Promise?

A Promise is an object that represents the eventual result of an async operation. Instead of passing a callback, the async function returns a Promise — a container that will eventually hold the result (or an error).
A Promise has three states:
Think of a Promise like a food delivery order. It starts as “pending” (being prepared). It either gets “fulfilled” (delivered successfully) or “rejected” (restaurant cancelled the order). You can’t change the outcome once it’s settled.

.then() — handling the result

Use .then() to specify what happens when the Promise fulfills:
.then() takes a function that receives the result. It also returns a new Promise, which is what enables chaining.

Chaining Promises

This is the key advantage over callbacks — sequential async operations stay flat:
Compare this to the nested callback version:
Each .then() returns a new Promise, so you can keep chaining. The data flows from one .then() to the next.

.catch() — handling errors

Use .catch() to handle errors anywhere in the chain:
A single .catch() at the end handles errors from any step in the chain. If fetch() fails, or response.ok is false, or .json() fails — the error flows down to .catch().
Always add .catch() at the end of a Promise chain. Without it, errors are silently swallowed. You’ll see “Uncaught (in promise)” warnings in the console, but your code won’t handle them.

.finally() — cleanup code

.finally() runs regardless of success or failure. Use it for cleanup:

Promise.all() — multiple requests in parallel

When you need data from multiple endpoints and they don’t depend on each other, run them in parallel:
Promise.all() takes an array of Promises and returns a single Promise that resolves when all of them complete. If any one fails, the whole thing rejects.
JavaScript’s Promise.all() is like Python’s asyncio.gather(). Same concept — run multiple async operations concurrently.

Creating your own Promises

You’ll rarely need to create Promises from scratch, but here’s how:
The new Promise() constructor takes a function with two parameters:
  • resolve(value) — call when the operation succeeds
  • reject(error) — call when the operation fails
Most of the time, you’ll consume Promises (from fetch, libraries, etc.), not create them. Focus on understanding .then(), .catch(), and Promise.all().

Why you’ll use async/await instead

Promises solved callback hell, but chaining .then() calls can still get verbose. The next step — async/await — lets you write the same async code in a way that reads like synchronous code:
async/await is built on top of Promises — it’s syntactic sugar, not a replacement. Understanding Promises helps you understand what async/await is doing under the hood.

What’s next?

Time to learn async/await — the modern syntax you’ll use 90% of the time for async operations.

Async/await

Write async code that reads like synchronous code