Course:Node.js & Express/
Lesson

Promises are the foundation of modern async JavaScript. Think of a PromiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits. like ordering food at a restaurant, you get a ticket (the Promise) immediately, and you can decide what to do when the food arrives (.then) or when the kitchen is out of ingredients (.catch). The ticket lets you go about your evening rather than standing at the counter waiting.

The three states of a promiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits.

Every Promise starts as pending and transitions exactly once to either fulfilled or rejected. Once settled, a Promise never changes state.

StateMeaningHandler
pendingOperation still running,
fulfilledCompleted successfully.then(value => ...)
rejectedFailed with an error.catch(error => ...)
const myPromise = new Promise((resolve, reject) => {
  // Simulate an async operation
  setTimeout(() => {
    const success = Math.random() > 0.5;

    if (success) {
      resolve('Data loaded!');   // -> fulfilled
    } else {
      reject(new Error('Load failed'));  // -> rejected
    }
  }, 1000);
});
The constructor body is synchronous. Only the resolve or reject calls happen later. This trips up many beginners, new Promise(...) does not pause your code.
02

Consuming a promiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits. with then and catch

Once you have a Promise, you attach handlers to it using .then() and .catch(). Each call returns a new Promise, which is what makes chaining possible.

fetchUserData(userId)
  .then(user => {
    console.log('User:', user.name);
    return fetchUserPosts(user.id);  // Return another Promise
  })
  .then(posts => {
    console.log('Posts:', posts.length);
  })
  .catch(error => {
    // Catches any error from the entire chain above
    console.error('Something failed:', error.message);
  })
  .finally(() => {
    // Runs whether success or failure
    console.log('Request finished');
  });

Think of .catch() like a safety net stretched under the entire chain. Any rejection above it falls straight down to the net.

03

Async/awaitWhat is async/await?A syntax that lets you write asynchronous code (like fetching data) in a readable, step-by-step style instead of chaining callbacks.: promises in disguise

async/await is syntax sugar over Promises. Under the hood, an async function always returns a PromiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits. and await pauses execution until a Promise settles. The code looks synchronous but behaves asynchronously.

async function loadUserData(userId) {
  try {
    const user = await fetchUserData(userId);
    console.log('User:', user.name);

    const posts = await fetchUserPosts(user.id);
    console.log('Posts:', posts.length);

    return { user, posts };
  } catch (error) {
    console.error('Something failed:', error.message);
    return null;
  } finally {
    console.log('Request finished');
  }
}

This is exactly equivalent to the chained version above, choose whichever reads more clearly for your situation.

04

Running promises in parallel

One common mistake is await-ing promises one by one when they do not depend on each other. That makes them sequential when they could run at the same time.

// Slow: three round-trips, sequential
const users    = await fetchUsers();
const posts    = await fetchPosts();
const comments = await fetchComments();

// Fast: all three start at the same time
const [users, posts, comments] = await Promise.all([
  fetchUsers(),
  fetchPosts(),
  fetchComments()
]);
Promise.all is all-or-nothing. If any Promise rejects, the whole thing rejects. If you want partial results, use Promise.allSettled instead.
05

Choosing the right parallel combinator

MethodResolves whenRejects whenUse case
Promise.allAll fulfillAny rejectsNeed all results
Promise.allSettledAll settleNeverWant all results, including failures
Promise.raceFirst settlesFirst rejectsTimeout pattern
Promise.anyFirst fulfillsAll rejectFirst successful result
// Promise.allSettled - get every result regardless of success
const results = await Promise.allSettled([
  fetchUser(1),
  fetchUser(2),
  fetchUser(999)  // May fail
]);

const successful = results
  .filter(r => r.status === 'fulfilled')
  .map(r => r.value);

// Promise.race - useful for timeouts
const timeout = new Promise((_, reject) =>
  setTimeout(() => reject(new Error('Timeout')), 5000)
);

const data = await Promise.race([fetchData(), timeout]);
06

Error handling patterns

There is no single correct way to handle PromiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits. errors. Here are the three patterns you will encounter most often.

// Pattern 1: try/catch - easiest to read
async function getData() {
  try {
    return await fetchData();
  } catch (err) {
    console.error('Failed:', err.message);
    return null;  // Return a safe fallback
  }
}

// Pattern 2: catch and rethrow with context
async function getUser(id) {
  try {
    return await fetchUser(id);
  } catch (err) {
    throw new Error(`Could not load user ${id}: ${err.message}`);
  }
}

// Pattern 3: chained fallbacks
fetchData()
  .catch(() => fetchBackupData())    // Try a backup source
  .catch(() => defaultData)          // Final fallback value
  .then(data => render(data));