# JavaScript Promises Explained for Beginners

## What problem do promises solve?

JavaScript is single-threaded. It runs one piece of code at a time, which means when you perform a slow operation, like fetching data from a server, reading a file, or waiting for a timer.

The entire thread can't afford to stop and wait. Instead, JavaScript uses an event-driven, asynchronous model: kick off the work, continue with other things, and handle the result when it's ready.

The earliest pattern for this was the **callback** you pass a function to another function, and that function calls yours back when the work completes. This works, but it breaks down fast. Imagine fetching a user, then fetching their posts, then fetching comments on each post. You end up nesting callbacks inside callbacks inside callbacks code that fans rightward across the screen, where error handling is duplicated at every level and the logical sequence of operations is buried in indentation. Developers called this callback hell.

Promises were introduced to solve this. A promise represents a value that isn't available *yet*, but will be in the future or won't arrive at all because something went wrong. Instead of passing callbacks into every function, you work with an object that carries the result of an asynchronous operation, with a clean, chainable API for handling success and failure.

## Promise states

Every promise lives in exactly one of three states at any given moment:

**Pending**: The operation is in progress. No result yet. This is the starting state.

**Fulfilled**: The operation completed successfully. The promise now holds the resolved value.

**Rejected:** The operation failed. The promise now holds a reason, typically an `Error` object explaining what went wrong.

The key rule: once a promise transitions out of pending, it never changes again. A fulfilled promise stays fulfilled forever. A rejected promise stays rejected. This predictability is one of the things that makes promises much easier to reason about than raw callbacks.

![](https://cdn.hashnode.com/uploads/covers/6950d1ca85602739c40abd67/fb72eabc-c603-4247-b60e-b84bb4d5917a.png align="center")

## Creating a promise

You create a promise by calling the `Promise` constructor and passing it an **executor**, a function that receives two callbacks: `resolve` and `reject`. You call one of them when the async work finishes.

```javascript
const fetchUser = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = true; // pretend this comes from a network call

    if (success) {
      resolve({ id: 1, name: 'Priya' }); // fulfills the promise
    } else {
      reject(new Error('User not found')); // rejects the promise
    }
  }, 1000);
});
```

The executor runs immediately when `new Promise(...)` is called. The resolve and reject callbacks settle the promise and trigger any handlers that have been attached.

## Handling success and failure

Once you have a promise, you react to its outcome using `.then()`, `.catch()`, and `.finally()`:

```javascript
fetchUser
  .then((user) => {
    console.log('Got user:', user.name); // runs if resolved
  })
  .catch((error) => {
    console.error('Failed:', error.message); // runs if rejected
  })
  .finally(() => {
    console.log('Done loading'); // always runs
  });
```

`.then()` receives the resolved value. `.catch()` receives the rejection reason. `.finally()` receives nothing.

It's purely for cleanup (hiding a loading spinner, closing a connection). A critical point: `.finally()` **doesn't consume the promise value**. Whatever the promise settled with flows through `.finally()` unchanged to any subsequent handler.

## Callbacks vs promises

Let's look at a concrete comparison. Three sequential async operations: load a user, then load their orders, then calculate their total spend.

```javascript
loadUser(userId, function(err, user) {
  if (err) return handleError(err);
  loadOrders(user.id, function(err, orders) {
    if (err) return handleError(err);
    calculateSpend(orders, function(err, total) {
      if (err) return handleError(err);
      console.log('Total spend:', total);
    });
  });
});
```

**With promises:**

```javascript
loadUser(userId)
  .then((user) => loadOrders(user.id))
  .then((orders) => calculateSpend(orders))
  .then((total) => console.log('Total spend:', total))
  .catch(handleError);
```

The logic is identical. But the promise version reads like a linear sequence of steps because it is one. Error handling is consolidated in a single `.catch()` at the end rather than duplicated at every level.

![](https://cdn.hashnode.com/uploads/covers/6950d1ca85602739c40abd67/d8787c3c-4730-491f-8b8c-4a0bab9e1705.png align="center")

## Promise chaining

Promise chaining is possible because `.then()` always returns a new promise. Whatever value you `return` from inside a `.then()` callback becomes the resolved value of that new promise, which the next `.then()` in the chain receives.

```javascript
fetchUser(1)
  .then((user) => {
    return fetchPostsByUser(user.id); // returns another promise
  })
  .then((posts) => {
    return posts.filter((p) => p.published); // returns a plain array
  })
  .then((publishedPosts) => {
    console.log(publishedPosts.length, 'published posts');
  })
  .catch((err) => {
    // any rejection in the chain above lands here
    console.error('Something went wrong:', err.message);
  });
```

The chain automatically unwraps promises: if you return a promise from inside `.then()`, the next `.then()` waits for that inner promise to settle before it fires. If you return a plain value, it's wrapped into a resolved promise automatically. This is the mechanism that makes chaining work seamlessly for both sync and async return values.

## A note on `async/await`

Modern JavaScript introduced `async/await` as syntax sugar built directly on top of promises. An `async` function always returns a promise, and `await` pauses execution inside that function until a promise settles without blocking the thread. It makes promise-based code look synchronous:

```javascript
async function loadDashboard(userId) {
  try {
    const user = await fetchUser(userId);
    const orders = await fetchOrders(user.id);
    const total = await calculateSpend(orders);
    console.log('Total spend:', total);
  } catch (err) {
    console.error('Failed:', err.message);
  }
}
```

This is the same sequence as the promise chain above, just written differently. Understanding promises deeply states, `.then()`, `.catch()`, chaining is essential before `async/await` makes sense, because under the hood, that's exactly what it's doing.
