Skip to content

What Is a Promise?

Most of the JavaScript you have written so far runs synchronously — line by line, in order, each line completing before the next one starts. That model breaks down the moment you need to wait for something: a network request, a timer, reading a file. Waiting synchronously would freeze the browser.

Promises are JavaScript’s solution to asynchronous operations — a way to say “start this work, and call me back when it’s done.”

Imagine fetching exchange rates from an API:

// Imaginary synchronous fetch — this is NOT how it works
const rates = fetchRates(); // this would freeze the browser for 1-2 seconds
console.log(rates);

If JavaScript waited here, nothing on the page could respond to user interaction — no scrolling, no button clicks, no animations. The browser would appear frozen.

Instead, JavaScript starts the network request and immediately moves on. When the response arrives (milliseconds or seconds later), a callback runs with the result. This is the event loop model.

A Promise is an object that represents the eventual result of an asynchronous operation. At any point in time, a Promise is in one of three states:

  • Pending — the operation has started but not yet completed
  • Fulfilled — the operation succeeded; the Promise has a result value
  • Rejected — the operation failed; the Promise has an error reason
const promise = fetch('https://open.er-api.com/v6/latest/USD');
// promise is immediately in the 'pending' state
// The network request is happening in the background

A Promise can only move from pending to either fulfilled or rejected — never backward, and only once. Once settled (fulfilled or rejected), it stays that way.

You can create a Promise with new Promise(executor). The executor is a function that receives two callbacks: resolve (call this on success) and reject (call this on failure):

function wait(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
wait(1000).then(() => console.log('1 second passed'));

In practice you rarely create Promises manually — you consume Promises returned by APIs like fetch. But seeing how they are constructed helps explain what “pending,” “fulfilled,” and “rejected” mean.

The Promise is not the value — it is a placeholder

Section titled “The Promise is not the value — it is a placeholder”

This distinction trips up every developer learning Promises:

const promise = fetch('https://open.er-api.com/v6/latest/USD');
console.log(promise); // Promise { <pending> } — NOT the response data

promise is not the exchange rate data. It is a Promise object. To get the data, you must tell the Promise what to do when it resolves — that is what .then() is for (next lesson).

The Promise model lets JavaScript stay non-blocking:

  1. You call an async function — it starts the operation and immediately returns a Promise
  2. Your code continues running (handling other events, updating the UI)
  3. When the operation completes, the Promise settles and your .then callback runs

The browser never freezes. The event loop keeps spinning. Your code stays responsive.

Open the browser console on any page and run:

const p = fetch('https://open.er-api.com/v6/latest/USD');
console.log(p);
console.log(typeof p);
console.log(p instanceof Promise);

Observe that:

  1. p logs as Promise { <pending> } (or similar)
  2. typeof p is 'object'
  3. p instanceof Promise is true

This confirms that fetch returns a Promise, not the response data.

  • JavaScript is single-threaded — waiting synchronously would freeze the page.
  • A Promise represents the eventual result of an async operation: pending → fulfilled or rejected.
  • You do not get the value from a Promise directly — you schedule work to run when it resolves.
  • fetch returns a Promise immediately. The actual network response arrives later.