Promise Chaining

Promise chaining lets asynchronous operations run in sequence by connecting promises with `then()` and `catch()`. Each `then()` or `catch()` returns a new Promise whose state is determined by what its handler returns or throws.

A Promise represents the eventual result of an asynchronous operation. Calling `then()` registers a fulfillment handler, and calling `catch()` registers a rejection handler; both methods return another Promise, which makes chains possible.

If a handler returns a normal value, the next Promise in the chain fulfills with that value. If it returns another Promise, the next Promise adopts that Promise's eventual state, allowing asynchronous operations to be sequenced without nesting callbacks.

If a handler throws an error or returns a rejected Promise, the next Promise becomes rejected. A later `catch()` can handle that rejection, and if the `catch()` handler returns normally, the chain becomes fulfilled again.

The value passed to each subsequent `then()` is therefore the resolved value produced by the previous step. This makes it possible to transform data step by step, while errors can propagate through the chain until a rejection handler handles them.

Key points

Interview questions

What happens when you return a value from a `then()` callback?

The `then()` call returns a new Promise. If the callback returns a normal value, that new Promise fulfills with the returned value. The next `then()` receives that value as its input. This is how synchronous transformations can be composed in a Promise chain.

What happens if a `then()` callback returns another Promise?

The Promise returned by `then()` adopts the state and eventual value of the returned Promise. If that Promise fulfills, the next handler receives its fulfillment value. If it rejects, the chain becomes rejected and can be handled by a later `catch()`. This behavior lets asynchronous operations be sequenced without nesting.

How does error propagation work in a Promise chain?

If a handler throws or returns a rejected Promise, the next Promise in the chain becomes rejected. Fulfillment handlers after that point are skipped until a rejection handler such as `catch()` is encountered. A `catch()` can handle the error and either return a recovery value or throw another error. This makes errors propagate through the chain in a predictable direction.

Why should you return the Promise from an asynchronous operation inside `then()`?

Returning the Promise connects that asynchronous operation to the chain. The following handler will then wait for it and receive its result. If the Promise rejects, the rejection also propagates through the chain. Without the return, the chain can continue before that operation finishes, and its rejection may not be handled by the intended downstream `catch()`.

Can a Promise chain recover after an error?

Yes. A rejection handler can return a normal value, which fulfills the Promise produced by that handler. Subsequent `then()` callbacks can then continue using the recovery value. If the handler throws or returns a rejected Promise instead, the chain remains rejected and the error continues to propagate.