JavaScript > Asynchronous JavaScript > Promises > Chaining promises

Promise Chaining in JavaScript

This example demonstrates how to chain promises in JavaScript to execute asynchronous operations sequentially. Promise chaining allows you to manage asynchronous code in a more readable and maintainable way by linking asynchronous tasks together.

Basic Promise Chaining

This code demonstrates a basic promise chain. Each .then() block receives the result of the previous promise and returns a new promise. The next .then() in the chain waits for this new promise to resolve before executing. The .catch() block handles any errors that occur in the chain.

// Function that returns a promise which resolves after a delay
function delayedResolve(value, delay) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(value);
    }, delay);
  });
}

// Chain promises together
delayedResolve('First', 1000)
  .then(result => {
    console.log(result); // Output: First
    return delayedResolve('Second', 500);
  })
  .then(result => {
    console.log(result); // Output: Second
    return delayedResolve('Third', 250);
  })
  .then(result => {
    console.log(result); // Output: Third
    console.log('All done!');
  })
  .catch(error => {
    console.error('An error occurred:', error);
  });

Concepts Behind Promise Chaining

Promise chaining works because the .then() method returns a new promise. If the function passed to .then() returns a value, the promise returned by .then() will resolve with that value. If the function passed to .then() returns a promise, the promise returned by .then() will 'adopt' the state of that returned promise. This means the promise returned by .then() will resolve or reject with the same value or reason as the promise returned by the function. This 'adoption' allows you to create a chain of dependent asynchronous operations.

Real-Life Use Case

Imagine you need to fetch data from an API, then process that data, and finally update the UI. Promise chaining is perfect for this scenario. You can chain these three operations together, ensuring they execute in the correct order. For example, first authenticate with API, then use the token received to get data from an endpoint, and finally display the received data on user interface.

Best Practices

  • Always include a .catch() block at the end of your promise chain to handle any errors that might occur.
  • Avoid deeply nested promise chains, as they can become difficult to read. Consider using async/await for more complex asynchronous flows.
  • Make sure each .then() handler returns a value or a promise. Returning undefined can lead to unexpected behavior.

Interview Tip

Be prepared to explain how promise chaining works under the hood. Understand that .then() always returns a promise, and the state of that promise depends on the value or promise returned by the .then() handler. Also understand the importance of error handling with .catch().

When to Use Them

Use promise chaining when you need to execute a series of asynchronous operations in a specific order, where each operation depends on the result of the previous one. Promise chaining is very efficient in cases like authentication, fetching, and processing of data.

Memory Footprint

Promise chains are generally memory-efficient, as each promise only exists for the duration of its execution. Once a promise resolves or rejects, its associated resources can be garbage collected. However, very long chains can potentially consume more memory, so consider refactoring into smaller, more manageable chunks if necessary. Use `async/await` which generally compiles to a more performant promise execution.

Alternatives

The main alternative to promise chaining is async/await, which provides a more synchronous-looking syntax for working with promises. async/await is often preferred for its improved readability, especially for complex asynchronous workflows. Another option is using Reactive Extensions (RxJS) which offers a more powerful and flexible approach to handling asynchronous data streams, but has a higher learning curve.

Pros

  • Improved code readability compared to callback hell.
  • Better error handling with a single .catch() block.
  • Clearer control flow for asynchronous operations.

Cons

  • Can still become complex and difficult to read with deeply nested chains.
  • Requires understanding of promises and asynchronous concepts.

FAQ

  • What happens if an error occurs in a promise chain?

    If an error occurs (either a synchronous error or a rejected promise) in a .then() handler, the promise chain will be short-circuited. The error will propagate down the chain until it reaches a .catch() handler, which will then handle the error.
  • Can I have multiple .catch() handlers in a promise chain?

    Yes, but generally you only need one .catch() handler at the end of the chain. Adding multiple .catch() handlers can make the code harder to understand. A .catch() handler will only be executed if an error occurs before it in the chain. If the error is handled, the promise returned by .catch() will resolve, and the chain continues (if there's something after the .catch() ).
  • How does async/await relate to promise chaining?

    async/await is syntactic sugar over promises. It allows you to write asynchronous code that looks and behaves a bit more like synchronous code. When you use await, you are essentially pausing the execution of the async function until the promise that you are awaiting resolves. It makes chaining easier to read.