Gyaan

Promises

intermediate promises async then catch

A Promise is an object that represents the eventual result of an asynchronous operation. Think of it like ordering food at a restaurant — you get a receipt (the promise) immediately, and the food (the result) comes later. The receipt can either be fulfilled (food arrives) or rejected (kitchen is closed).

Promise States

A Promise is always in one of three states:

Pending
initial state, waiting
Fulfilled
resolve() was called
or
Rejected
reject() was called
Once settled (fulfilled or rejected), a Promise can never change state again.

Creating a Promise

We create a Promise using the new Promise() constructor. It takes a function with two parameters: resolve (for success) and reject (for failure).

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

  if (success) {
    resolve("It worked!"); // fulfilled
  } else {
    reject("Something went wrong"); // rejected
  }
});

Consuming a Promise: .then(), .catch(), .finally()

  • .then(callback) — runs when the Promise is fulfilled
  • .catch(callback) — runs when the Promise is rejected
  • .finally(callback) — runs no matter what (fulfilled or rejected)
myPromise
  .then(result => console.log(result))   // "It worked!"
  .catch(error => console.log(error))     // runs if rejected
  .finally(() => console.log("Done!"));   // always runs

Promise Chaining

The real magic of Promises is chaining. Whatever we return from a .then() gets passed as the input to the next .then(). This flattens the callback hell into a clean chain.

getUser(userId)
  .then(user => getOrders(user.id))
  .then(orders => getOrderDetails(orders[0].id))
  .then(details => getShippingInfo(details.trackingId))
  .then(shipping => console.log(shipping.status))
  .catch(error => console.log("Something failed:", error));

Compare this with the callback hell version — night and day difference. Each .then() returns a new Promise, so we can keep chaining.

Error Propagation

One of the best things about Promises is that a single .catch() at the end catches errors from any step in the chain. If step 2 fails, it skips all remaining .then() calls and jumps straight to .catch().

fetchData()
  .then(data => processData(data))    // if this throws...
  .then(result => saveResult(result))  // ...this is skipped
  .then(() => console.log("Saved!"))   // ...this is skipped too
  .catch(error => {
    console.log("Caught:", error);     // ...and we land here
  });

Converting Callbacks to Promises

We can wrap any callback-based function in a Promise. This is a very useful pattern:

// Callback-based
function loadData(url, callback) {
  fetch(url, (err, data) => callback(err, data));
}

// Promise-based wrapper
function loadData(url) {
  return new Promise((resolve, reject) => {
    fetch(url, (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
}

// Now we can use it with .then()
loadData("/api/users")
  .then(data => console.log(data))
  .catch(err => console.log(err));

Quick tips

  • A .then() can take two arguments: then(onFulfilled, onRejected) — but using .catch() is cleaner and more readable.
  • If we return a plain value from .then(), the next .then() gets it wrapped in a resolved Promise automatically.
  • If we return a Promise from .then(), the next .then() waits for it to settle.

In simple language, Promises give us a way to handle async operations without the nesting nightmare. We chain .then() calls for sequential steps and use a single .catch() at the end for errors. They were such a big improvement over callbacks that they became the foundation for async/await.