Promise Resolution and Thenable Assimilation

Promise resolution recursively adopts the eventual state of a returned Promise or thenable instead of treating that object as an ordinary value. This explains why `then` chains can flatten nested asynchronous results and why arbitrary thenables can participate in Promise chains.

When a Promise is resolved with a value, the value is not always stored directly. If it is a Promise or a thenable—an object or function with a callable `then` property—the resolving Promise attempts to adopt that object's eventual state.

This process is recursive. For example, resolving `p1` with `p2` makes `p1` follow `p2`; if `p2` is fulfilled with another thenable, the adoption continues until a non-thenable value is reached or an error/rejection occurs. This is why returning `Promise.resolve(value)` from a `.then()` callback does not create a permanently nested Promise.

A thenable is not required to be an actual native Promise. JavaScript checks for a callable `then` property and, when present, invokes it with resolution and rejection callbacks. Because accessing `then` or invoking it can itself fail, the resolution procedure must handle exceptions and settle the target Promise accordingly.

The procedure also protects against self-resolution. A Promise cannot be resolved with itself, because that would require its state to depend on its own state indefinitely; attempting this results in rejection with a `TypeError`.

For frontend interviews, the important distinction is between resolving a Promise and fulfilling it with an arbitrary value. Resolution may be asynchronous in effect because the resolved value can represent another computation whose state must first be adopted.

Key points

Interview questions

What happens when you call `Promise.resolve()` with another Promise?

The returned Promise adopts the state of the supplied Promise rather than fulfilling with that Promise object as its value. If the supplied Promise fulfills, the returned Promise fulfills with the same fulfillment value. If it rejects, the returned Promise rejects with the same reason. This is part of Promise resolution and is what allows asynchronous chains to flatten naturally.

What is a thenable, and how does Promise resolution handle one?

A thenable is an object or function with a callable `then` property. During resolution, the Promise machinery retrieves that property and, when it is callable, invokes it using resolution and rejection functions. The resulting Promise then adopts whatever outcome the thenable produces. This interoperability allows Promise chains to work with Promise-like implementations without requiring the object to be a native Promise.

Why doesn't returning a Promise from `.then()` create a nested Promise?

The Promise returned by `.then()` resolves with the callback's return value. If that value is another Promise or thenable, the returned Promise assimilates it instead of treating it as an ordinary object. Therefore, the eventual fulfillment value is exposed directly through the chain. This recursive adoption is what gives Promise chains their flattened behavior.

What happens if a Promise is resolved with itself?

Self-resolution is rejected with a `TypeError`. Otherwise the Promise would need to determine its state by waiting for itself, creating an impossible circular dependency. The resolution procedure explicitly prevents this case. This is different from resolving with a different Promise, where normal state adoption is possible.

Why can a badly behaved thenable affect Promise resolution?

Thenables are user-defined objects, so their `then` getter or method can execute arbitrary code and throw exceptions. The resolution procedure therefore has to guard access to `then` and the call into the thenable. It also ensures that the first effective settlement wins when the supplied callbacks are invoked multiple times. These rules let native Promises interoperate with imperfect Promise-like implementations while preserving predictable settlement behavior.