Skip to main content

🤞 Promises

I promise to call you when I'm done

The Restaurant Ticket Analogy

You're at a busy restaurant. You order food at the counter.

What happens:

  1. You order (make a request)
  2. You get a ticket with a number (not the food itself!)
  3. You sit down and wait
  4. When ready, they call your number

The ticket is a promise. It doesn't give you food. It's like an agreement that:

  • You WILL get your food eventually (fulfilled), OR
  • They'll tell you if something went wrong (rejected)

JavaScript Promises work the same way.

A Promise is a placeholder for a future value. Instead of freezing while waiting for slow operations, you get a Promise - an object that will eventually contain your result (or an error).


Why Promises Exist

The Problem: Callback Hell

Before Promises, handling async operations meant nested callbacks:

getUser(function(user) {
    getOrders(user, function(orders) {
        getDetails(orders, function(details) {
            // 3 levels deep already!
        });
    });
});

This "pyramid of doom" was hard to read, write, and maintain.

Promises flatten the code:

getUser()
  .then(user => getOrders(user))
  .then(orders => getDetails(orders))
  .then(details => doSomething(details))
  .catch(error => handleError(error));

Same logic, but readable!


The Three States

A Promise is in one of three states at a time:

StateMeaningWhat Happens
PendingWaitingOperation in progress
FulfilledSuccess.then() handler runs
RejectedFailed.catch() handler runs
Promise created → PENDING
        ↓
    (waiting)
        ↓
  Result ready?
   ↙        ↘
SUCCESS    ERROR
   ↓          ↓
FULFILLED  REJECTED

Once a Promise settles (fulfilled or rejected), it cannot change - it's locked in that state forever.


How to Use Promises

Step 1: Get a Promise

const promise = fetch("/api/users"); // Returns a Promise

Step 2: Handle success with .then()

promise.then((result) => {
  console.log("Got it!", result);
});

Step 3: Handle errors with .catch()

promise
  .then((result) => console.log(result))
  .catch((error) => console.log("Failed:", error));

Chaining: One After Another

Promises can chain - each .then() returns a new Promise:

fetch('/api/user')
  .then(response => response.json())      // Returns Promise
  .then(user => fetch('/api/orders/' + user.id))  // Returns Promise
  .then(response => response.json())      // Returns Promise
  .then(orders => console.log(orders))    // Final result
  .catch(error => console.log(error));    // Catches ANY error above

The chain flows top to bottom. If any step fails, it jumps straight to .catch().


Creating Your Own Promises

Sometimes you need to wrap non-Promise operations:

const myPromise = new Promise((resolve, reject) => {
  // Do something async...

  if (success) {
    resolve(result); // Fulfill with result
  } else {
    reject(error); // Reject with error
  }
});

Example: Wrapping setTimeout

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

wait(delayMs).then(() => console.log("Done waiting!"));

Running Multiple Promises

Promise.all(): Wait for ALL to finish

Promise.all([fetchUsers(), fetchProducts(), fetchOrders()])
  .then(([users, products, orders]) => {
    // All three finished!
  });

If any fails, the entire thing fails.

Promise.race(): First one wins

Promise.race([fetchFromServer1(), fetchFromServer2()])
  .then(result => {
    // First one to finish, use that result
  });

Promise.allSettled(): Get all results (even failures)

Promise.allSettled([mayFail1(), mayFail2(), mayFail3()])
  .then(results => {
    // Each result has { status, value } or { status, reason }
  });

Real-World Examples

1. Fetching Data

1. User clicks "Load Data"
2. fetch() returns a Promise
3. Page shows loading spinner
4. Promise fulfills → Show data
   OR Promise rejects → Show error message

2. Form Submission

1. User submits form
2. Send to server (returns Promise)
3. Show "Submitting..."
4. Promise fulfills → "Success!"
   OR Promise rejects → "Please try again"

3. Image Loading

1. Start loading image
2. Promise pending...
3. Image loads → Display it
   OR Image fails → Show placeholder

Common Mistakes

Mistake 1: Forgetting to Return in Chains

// ❌ Buggy chain - forgot return!
fetch("/api/user")
  .then((response) => {
    response.json(); // No return!
  })
  .then((user) => {
    console.log(user); // undefined!
  });

// ✓ Correct
fetch("/api/user")
  .then((response) => response.json()) // Returns the Promise
  .then((user) => console.log(user)); // Gets the result

Mistake 2: Not Catching Errors

// ❌ Unhandled rejection!
fetch("/api/data").then((response) => response.json());
// What if it fails?

// ✓ Handle errors
fetch("/api/data")
  .then((response) => response.json())
  .catch((error) => console.log("Failed:", error));

Mistake 3: Nested .then() Calls

// ❌ Back to callback hell!
fetch("/user").then((user) => {
  fetch("/orders/" + user.id).then((orders) => {
    // Nested again!
  });
});

// ✓ Chain properly
fetch("/user")
  .then((user) => fetch("/orders/" + user.id))
  .then((orders) => console.log(orders));

Promises vs Async/Await

Async/await is just cleaner syntax for Promises:

PromisesAsync/Await
.then() chainsawait statements
.catch() for errorstry/catch blocks
Explicit Promise handlingReads like sync code

Same thing, different syntax:

// Promises
getUser()
  .then((user) => getOrders(user))
  .then((orders) => console.log(orders));

// Async/Await (equivalent)
const user = await getUser();
const orders = await getOrders(user);
console.log(orders);

FAQ

Q: What's the difference between Promise and callback?

Promises are objects representing future values. Callbacks are functions you pass to be called later. Promises provide cleaner chaining and error handling.

Q: Can a Promise be fulfilled AND rejected?

No. Once a Promise settles (either state), it's locked. It can't change.

Q: What happens if I don't handle a rejected Promise?

Many runtimes show an "unhandled rejection" warning. It's a good idea to handle rejections (with .catch() or try/catch around await).

Q: When should I use Promise.all()?

When you need multiple independent operations to ALL complete before proceeding.

Q: Can I cancel a Promise?

Standard Promises can't be cancelled. AbortController can be used with fetch() for cancellation.

Q: Should I use Promises or async/await?

Async/await is usually cleaner for most cases. Use raw Promises for advanced patterns like Promise.all().


Summary

Promises are objects that represent eventual completion (or failure) of an async operation. They flatten callback hell into readable chains.

Key Takeaways:

  • Promise = placeholder for a future value
  • Three states: pending, fulfilled, rejected
  • .then() for success, .catch() for errors
  • Chain promises for sequential operations
  • Promise.all() for parallel operations
  • Handle rejections.
  • Async/await is cleaner syntax for the same thing

Promises are fundamental to modern JavaScript. Master them and async code becomes straightforward!

Leave a Comment

Comments (0)

Be the first to comment on this concept.

Comments are approved automatically.