Promises: resolve, reject, .then, .catch

Learn how JavaScript Promises work to handle asynchronous operations with resolve, reject, and chaining using .then and .catch.

Loading...
Promises: resolve, reject, .then, .catch

When you're dealing with tasks that take time, like fetching data from a server or reading a file, you need a way to handle them without blocking your code. That's where Promises come in.

What is a Promise?

A Promise in JavaScript is an object that represents a value that may be available now, or in the future, or never.

It helps you handle asynchronous operations in a clean and readable way, avoiding callback hell.

You can think of it like ordering food at a restaurant, you get a receipt (the promise) that guarantees your food will either arrive successfully or something will go wrong.

A Promise can be in one of three states:

  • Pending: The operation is still running (your food is being prepared).
  • Fulfilled/Resolved: The operation completed successfully (your food arrived).
  • Rejected: The operation failed (the kitchen ran out of ingredients).

Creating a Promise

You create a Promise using the new Promise constructor which takes a function called the executor.

The executor function has two arguments:

  • resolve: Call this when your async task is successful.
  • reject: Call this when your async task fails or errors out.

Syntax:

const myPromise = new Promise((resolve, reject) => {
  // Do some async work
 
  if (/* operation successful */) {
    resolve("Success!");
  } else {
    reject("Error message!");
  }
});

Example:

const coinFlip = new Promise((resolve, reject) => {
  const random = Math.random();
 
  if (random > 0.5) {
    resolve("Heads!");
  } else {
    reject("Tails!");
  }
});
 
console.log(coinFlip); // Promise { <pending> }

Here,

  • coinFlip is a Promise that simulates flipping a coin.
  • Math.random() generates a random number between 0 and 1.
  • If it's greater than 0.5, you resolve with "Heads!".
  • If it's 0.5 or less, you reject with "Tails!".
  • When you log the promise immediately, it shows as Promise { <pending> } because the operation hasn't completed yet.

Handling Promises with .then() and .catch()

Once you have a Promise, you need to handle its result.

To handle the result from a Promise, you have .then and .catch:

  • .then(): Runs when the Promise resolves (succeeds).
  • .catch(): Runs when the Promise rejects (fails).

Syntax:

promise
  .then((result) => {
    // Handle success
  })
  .catch((error) => {
    // Handle failure
  });

Example:

const coinFlip = new Promise((resolve, reject) => {
  const random = Math.random();
 
  if (random > 0.5) {
    resolve("Heads!");
  } else {
    reject("Tails!");
  }
});
 
coinFlip
  .then((result) => {
    console.log("Success:", result);
  })
  .catch((error) => {
    console.log("Failed:", error);
  });
 
// Output:
 
// Success: Heads!
// OR
// Failed: Tails!

Here,

  • If the coin flip resolves with "Heads!", the .then() block runs.
  • If it rejects with "Tails!", the .catch() block runs.
  • Only one of them will execute, never both.

Practical Example

function fetchUserData(userId) {
  return new Promise((resolve, reject) => {
    // Simulate an API call
    setTimeout(() => {
      if (userId === 1) {
        resolve({ id: 1, name: "Shefali", email: "connect@shefali.dev" });
      } else {
        reject("User not found");
      }
    }, 1000);
  });
}
 
fetchUserData(1)
  .then((user) => {
    console.log("User found:", user);
  })
  .catch((error) => {
    console.log("Error:", error);
  });
 
// Output:
// Promise { <state>: "pending" }
// After 1 second:
// User found: { id: 1, name: "Shefali", email: "connect@shefali.dev" }

Here,

  • fetchUserData returns a Promise that simulates fetching user data.
  • It uses setTimeout to simulate network delay.
  • If userId is 1, it resolves with user data.
  • Otherwise, it rejects with an error message.
  • The .then() and .catch() handle the success and failure cases.

Chaining Promises

You can chain multiple .then calls to handle multiple async steps in order. Each .then() returns a new Promise.

function fetchUserData(userId) {
  return new Promise((resolve, reject) => {
    // Simulate an API call
    setTimeout(() => {
      if (userId === 1) {
        resolve({ id: 1, name: "Shefali", email: "connect@shefali.dev" });
      } else {
        reject("User not found");
      }
    }, 1000);
  });
}
 
fetchUserData(1)
  .then((user) => {
    console.log("Got user:", user.name);
    return user.id; // This becomes the value for the next .then()
  })
  .then((userId) => {
    console.log("User ID is:", userId);
    return `Processing user ${userId}`; // This becomes the value for the next .then()
  })
  .then((message) => {
    console.log(message);
  })
  .catch((error) => {
    console.log("Something went wrong:", error);
  });
 
// Output:
// Promise { <state>: "pending" }
// After 1 second:
// Got user: Shefali
// User ID is: 1
// Processing user 1

Here,

  • The first .then() receives the user object and returns user.id.
  • The second .then() receives the user ID and returns a message.
  • The third .then() receives the message and logs it.
  • If any step fails, the .catch() at the end will handle it.

Note: If any .then or the original Promise rejects, .catch will run.


Promise.resolve() and Promise.reject()

Sometimes you want to create a Promise that's already resolved or rejected.

// Already resolved
const resolvedPromise = Promise.resolve("Already done!");
 
resolvedPromise.then((value) => {
  console.log(value); // Output: "Already done!"
});
 
// Already rejected
const rejectedPromise = Promise.reject("Already failed!");
 
rejectedPromise.catch((error) => {
  console.log(error); // Output: "Already failed!"
});

Here,

  • Promise.resolve() creates a Promise that's immediately resolved with the given value.
  • Promise.reject() creates a Promise that's immediately rejected with the given reason.

When to use this:

  • When you want to convert a regular value into a Promise.
  • When you need to return a Promise from a function but the result is already known.
  • For testing or mocking asynchronous operations.

Real-World Example

function fetchWeather(city) {
  return new Promise((resolve, reject) => {
    // Simulate checking if city is valid
    if (!city || city.trim() === "") {
      reject("City name is required");
      return;
    }
 
    // Simulate API call
    setTimeout(() => {
      if (city.toLowerCase() === "london") {
        resolve({
          city: "London",
          temperature: 22,
          condition: "Sunny",
        });
      } else {
        reject(`Weather data not found for ${city}`);
      }
    }, 1500);
  });
}
 
fetchWeather("London")
  .then((weather) => {
    console.log(`Weather in ${weather.city}:`);
    console.log(`Temperature: ${weather.temperature}°C`);
    console.log(`Condition: ${weather.condition}`);
  })
  .catch((error) => {
    console.log("Failed to get weather:", error);
  });
 
// Output:
// Promise { <state>: "pending" }
 
// After 1.5 seconds:
// Weather in London:
// Temperature: 22°C
// Condition: Sunny

Here,

  • fetchWeather simulates calling a weather API.
  • It first validates the input, if no city is provided, it rejects immediately.
  • It uses setTimeout to simulate network delay.
  • If the city is 'London', it resolves with weather data.
  • Otherwise, it rejects with an error message.
  • The .then() displays the weather data.
  • The .catch() handles any errors that occur.

Handling Multiple Promises

Promise.all()

It runs multiple Promises in parallel and waits for all to complete.

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
 
Promise.all([promise1, promise2, promise3])
  .then((results) => {
    console.log(results); // Output: [1, 2, 3]
  })
  .catch((error) => {
    console.log("One of them failed:", error);
  });

Note: If any Promise rejects, the entire Promise.all() rejects immediately.

Promise.allSettled()

It waits for all Promises to complete, regardless of success or failure.

const promise1 = Promise.resolve("Success");
const promise2 = Promise.reject("Failed");
 
Promise.allSettled([promise1, promise2]).then((results) => {
  console.log(results);
});
 
// Output:
// [
// { status: 'fulfilled', value: 'Success' },
// { status: 'rejected', reason: 'Failed' }
// ]

Common Mistakes

  • Forgetting to return values in .then():

    // Wrong
    promise
      .then((data) => {
        processData(data); // Not returning anything
      })
      .then((result) => {
        console.log(result); // undefined
      });
     
    // Right
    promise
      .then((data) => {
        return processData(data); // Return the result
      })
      .then((result) => {
        console.log(result); // Actual result
      });
  • Not handling errors:

    // This can cause unhandled promise rejections
    fetchData().then((data) => {
      console.log(data);
    });
    // Add .catch() to handle errors
  • Creating unnecessary Promises: If you already have a Promise, don't wrap it in another one.

    // Don't do this
    function fetchData() {
      return new Promise((resolve, reject) => {
        fetch("/api/data")
          .then((response) => resolve(response))
          .catch((error) => reject(error));
      });
    }
     
    // Do this
    function fetchData() {
      return fetch("/api/data");
    }

Best Practices

  • Always handle both success and failure cases with .then() and .catch().
  • Keep your Promise chains flat. Avoid nesting .then() calls inside other .then() calls.
  • Return values from .then() to pass data to the next step in the chain.
  • Use Promise.all() when you need to wait for multiple independent operations.
  • Use descriptive error messages when calling reject().
  • Consider using async/await for more readable asynchronous code.

Support my work!