Promises in JavaScript

Promises in JavaScript

Introduction

Promises in JavaScript are a powerful tool for managing asynchronous operations, allowing developers to write cleaner, more manageable code.

What is a Promise?

A Promise is an object representing the eventual completion or failure of an asynchronous operation. A Promise holds a value that might be available now, later, or never. It represents the idea of promising to do something. Promises have three states:

  • Pending: Initial state, neither fulfilled nor rejected.

  • Fulfilled: The operation completed successfully.

  • Rejected: The operation failed.

Consider the following example where a Promise resolves another Promise:

new Promise((resolveOuter) => {
  resolveOuter(
    new Promise((resolveInner) => {
      setTimeout(resolveInner, 1000);
    })
  );
});

In this case, the outer Promise is pending until the inner Promise resolves. The completion of the inner Promise after 1 second fulfills the outer Promise.

Chaining Promises

Promises can be chained to perform sequential asynchronous operations. Methods like .then(), .catch(), and .finally() enable this chaining:

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("foo");
  }, 300);
});

myPromise
  .then(handleFulfilledA)
  .then(handleFulfilledB)
  .then(handleFulfilledC)
  .catch(handleRejected)
  .finally(() => console.log('Operation completed.'));
  • .then() executes a callback on Promise fulfillment or rejection.

  • .catch() is a shorthand for handling rejections.

  • .finally() executes after the Promise settles, regardless of its outcome.

Handling Multiple Promises

JavaScript provides several methods to deal with multiple Promises concurrently:

  • Promise.all() waits for all promises to be resolved or for any to be rejected. Helpful for combining the outcomes of several promises.

      Promise.all([promise1, promise2]).then((results) => {
        const [result1, result2] = results;
        console.log(result1, result2);
      });
    
  • Promise.allSettled() waits for all promises to settle, regardless of the outcome. Each promise's result is provided, indicating success or failure.

      Promise.allSettled([promise1, promise2]).then((results) => results.forEach((result) => console.log(result.status)));
    
  • Promise.race() waits for the first promise to settle, either fulfilled or rejected. This is useful for timeout patterns.

      Promise.race([promise1, promise2]).then((result) => console.log(result));
    
  • Promise.any() waits for the first promise to fulfill, ignoring all rejections unless all promises are rejected.

      Promise.any([promise1, promise2]).then((result) => console.log(result));
    

Async/Await: Syntactic Sugar for Promises

Async/await syntax offers a cleaner, more readable way to work with Promises, making asynchronous code appear synchronous:

async function fetchData() {
  try {
    const data = await fetch('https://api.example.com/data');
    console.log(await data.json());
  } catch (error) {
    console.error('Failed to fetch data:', error);
  }
}
  • An async function implicitly returns a Promise.

  • The await keyword pauses the function execution until the Promise settles.

  • Use try/catch blocks to handle potential rejections.

Concurrent Execution with Async/Await

To run Promises concurrently within an async function, utilize Promise.all():

async function fetchMultipleData() {
  try {
    const [dataA, dataB] = await Promise.all([fetchDataA(), fetchDataB()]);
    console.log(dataA, dataB);
  } catch (error) {
    console.error('Failed to fetch data:', error);
  }
}

This approach ensures that dataA and dataB are fetched in parallel, optimizing performance.