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/.catchor, more readably,async/awaitwithtry/catch. - Use
Promise.allto run independent work in parallel. - Always handle errors; remember
fetchdoesn'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
"ReferenceError: window is not defined" in Next.js: How to Fix It
window, document and localStorage only exist in the browser. Next.js renders components on the server first, where they don't exist. Why it happens, and the fixes: useEffect, 'use client', typeof checks, dynamic imports with ssr: false, and libraries that touch window on import.
What Is TypeScript? JavaScript With Labels, Explained for Beginners
TypeScript is JavaScript plus types: labels that say what kind of value each thing holds, checked before your code runs. What it looks like, why AI tools default to it, what .ts and .tsx files are, and how to read the red squiggles.