Blog
3 min read

JavaScript Promises Explained: then, catch, and Promise.all

A promise is JavaScript's way of saying 'I'll have the answer later'. What promises are, the three states, .then and .catch, how async/await relates, running things in parallel with Promise.all and allSettled, and the mistakes that cause unhandled rejections.

Lots of things in JavaScript take time: fetching data from an API, reading a file, querying a database. JavaScript doesn't stop and wait. Instead, these functions return a promise — an object that represents a value that will arrive later (or an error, if it fails).

The three states

A promise is always in one of three states:

  • pending — still working
  • fulfilled — finished successfully, with a value
  • rejected — failed, with an error

Once it's fulfilled or rejected, it stays that way.

Using a promise with .then and .catch

fetch('/api/user')
  .then(response => response.json())
  .then(user => {
    console.log(user.name)
  })
  .catch(error => {
    console.error('Something went wrong:', error)
  })
  .finally(() => {
    hideSpinner()
  })
  • .then() runs when the previous step succeeds. Whatever you return is passed to the next .then().
  • .catch() runs if any step before it fails.
  • .finally() runs either way.

async/await: the same thing, easier to read

Most modern code uses async/await, which is built on promises:

async function loadUser() {
  try {
    const response = await fetch('/api/user')
    const user = await response.json()
    console.log(user.name)
  } catch (error) {
    console.error('Something went wrong:', error)
  } finally {
    hideSpinner()
  }
}

await pauses this function (not the whole page) until the promise settles. An async function always returns a promise. (Async/await explained)

Running things at the same time

Promise.all — wait for all, fail if any fails

const [user, orders] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/orders').then(r => r.json()),
])

Both requests run in parallel. If you await them one after another instead, the second doesn't start until the first finishes — twice as slow.

Promise.allSettled — wait for all, never fail

const results = await Promise.allSettled([sendEmail(), sendSms()])
// [{status: 'fulfilled', value: ...}, {status: 'rejected', reason: ...}]

Use it when some can fail and you still want the others.

Promise.race and Promise.any

  • race — settles with whichever finishes first (success or failure). Handy for timeouts.
  • any — the first one to succeed.

Creating your own promise

You rarely need to, but it helps to see one:

function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms))
}

await wait(1000) // pause for one second

Common mistakes

1. Forgetting await.

const user = fetch('/api/user') // a Promise, not the data
console.log(user.name)          // undefined

If something is unexpectedly undefined or logs as Promise {<pending>}, a missing await is the usual cause.

2. Not handling errors. A rejected promise with no .catch() or try/catch causes an unhandled promise rejection — a console error in the browser, and in Node.js it can crash the process.

3. await inside .forEach(). forEach doesn't wait. Use for...of for one-at-a-time, or Promise.all(items.map(...)) for parallel.

4. fetch doesn't reject on 404 or 500. It only rejects on network failure. Check response.ok yourself. (TypeError: Failed to fetch)

const response = await fetch('/api/user')
if (!response.ok) throw new Error(`HTTP ${response.status}`)

The summary

  • A promise is a value that arrives later: pending → fulfilled or rejected.
  • Handle it with .then/.catch or, more readably, async/await with try/catch.
  • Use Promise.all to run independent work in parallel.
  • Always handle errors; remember fetch doesn't throw on HTTP errors.

EasySpawn runs your JavaScript app on a server with logs one click away, so unhandled rejections and failed requests are easy to track down — with Claude Code on hand to fix them. See how it works or join the waitlist.

Related: Async/Await in JavaScript · Fetch vs Axios · TypeError: Failed to Fetch · JavaScript Array Methods

Keep reading