JavaScript11 min read

JavaScript Promises: Handling Async Operations

Master JavaScript Promises. Understand async operations, promise chaining, error handling, and Promise.all.

Alex Thompson
December 19, 2025
0.0k0

JavaScript Promises

The Problem: Things That Take Time

Imagine you order food online. You don't stand at the door waiting - you do other things, and when the food arrives, you handle it.

JavaScript works the same way. Some things take time:

┌─────────────────────┐
│  You: Fetch data    │
│  from API           │
│  (takes 2 seconds)  │
└──────────┬──────────┘
            │
┌───────────▼───────────┐
│  JavaScript doesn't   │
│  wait! It continues   │
│  with other code     │
└───────────────────────┘
            │
┌───────────▼───────────┐
│  2 seconds later...    │
│  API responds         │
│  Now what?             │
└───────────────────────┘

The problem: How do you handle the response when it arrives? Promises solve this.

What is a Promise?

A Promise is like a receipt. You get it immediately, but the actual result comes later.

Promise Lifecycle:
┌─────────────────────┐
│  1. Pending         │ ← Just created, waiting
│     (Waiting...)     │
└──────────┬──────────┘
            │
    ┌───────┴───────┐
    │               │
┌───▼───┐      ┌───▼───┐
│ 2.    │      │ 3.    │
│Fulfilled│    │Rejected│
│(Success)│    │(Error) │
└────────┘      └───────┘

Three states:

  • Pending: Waiting for result
  • Fulfilled: Got the result (success)
  • Rejected: Something went wrong (error)

Step-by-Step: Creating a Promise

Step 1: Create a new Promise

const myPromise = new Promise((resolve, reject) => {
  
});

Step 2: Add your async operation

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    // This runs after 1 second
  }, 1000);
});

Step 3: Call resolve on success

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Data loaded!');
  }, 1000);
});

Step 4: Call reject on error

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('Success!');
    } else {
      reject('Failed!');
    }
  }, 1000);
});

Step-by-Step: Using a Promise

Step 1: Call .then() for success

myPromise.then(result => {
  console.log(result); // "Success!"
});

Step 2: Add .catch() for errors

myPromise
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error(error);
  });

What happens:

1. Promise starts (Pending)
   │
2. After 1 second...
   │
3. If success → .then() runs
   If error → .catch() runs

Promise Chaining: One After Another

Sometimes you need to do things in order:

fetch('https://api.example.com/user')
  .then(response => response.json())
  .then(user => {
    console.log('Got user:', user);
    return fetch(`https://api.example.com/posts/${user.id}`);
  })
  .then(response => response.json())
  .then(posts => {
    console.log('Got posts:', posts);
  })
  .catch(error => {
    console.error('Something failed:', error);
  });

Flow diagram:

Step 1: Fetch user
    │
    ▼
Step 2: Convert to JSON
    │
    ▼
Step 3: Get user ID, fetch posts
    │
    ▼
Step 4: Convert posts to JSON
    │
    ▼
Step 5: Use the posts

Each .then() waits for the previous one to finish.

Promise.all: Do Multiple Things at Once

When you need to wait for multiple things:

Promise.all([
  fetch('/api/users'),
  fetch('/api/posts'),
  fetch('/api/comments')
])
.then(([users, posts, comments]) => {
  console.log('All done!');
  console.log('Users:', users);
  console.log('Posts:', posts);
  console.log('Comments:', comments);
})
.catch(error => {
  console.error('One of them failed:', error);
});

How it works:

┌─────────────┐
│  Start all  │
│  3 requests │
└──────┬──────┘
       │
   ┌───┴───┐
   │       │       │
┌──▼──┐ ┌──▼──┐ ┌──▼──┐
│User │ │Post │ │Comm │
│API  │ │API  │ │API  │
└──┬──┘ └──┬──┘ └──┬──┘
   │       │       │
   └───┬───┴───┬───┘
       │       │
┌───────▼───────▼───────┐
│  Wait for ALL         │
│  to finish            │
└───────────────────────┘
       │
┌───────▼───────┐
│  Then run code│
└───────────────┘

Important: If any promise fails, the whole thing fails.

Real Example: Loading User Data

function loadUserData(userId) {
  return fetch(`/api/users/${userId}`)
    .then(response => {
      if (!response.ok) {
        throw new Error('User not found');
      }
      return response.json();
    })
    .then(user => {
      console.log('User loaded:', user.name);
      return user;
    })
    .catch(error => {
      console.error('Failed to load user:', error);
      throw error;
    });
}

// Use it
loadUserData(123)
  .then(user => {
    console.log('Got user:', user);
  });

Common Patterns

Pattern 1: Handle errors properly

promise
  .then(result => {
    // Success
  })
  .catch(error => {
    // Always handle errors!
    console.error(error);
  });

Pattern 2: Return promises in .then()

fetch('/api/data')
  .then(response => response.json())
  .then(data => {
    return fetch(`/api/more/${data.id}`); // Return next promise
  })
  .then(response => response.json());

Quick Tips

  1. Always handle errors - Use .catch() or your app might break silently
  2. Return promises in .then() - This lets you chain them
  3. Use Promise.all for parallel operations - Faster than doing them one by one
  4. Promises are everywhere - fetch(), setTimeout(), file operations all return promises

Promises are essential for modern JavaScript. They let you handle async operations cleanly. Once you understand promises, async/await will make much more sense.

#JavaScript#Promises#Async#Intermediate