Volume 2

Q110: Promises, Thenables and the Resolution Procedure

Difficulty: SeniorFrequency: HighAnswer time: 10-14 minutes

What Interviewers Want To Evaluate

This question tests whether you understand promises beyond basic then and catch. Interviewers want promise states, settlement, chaining, thenable assimilation, microtask scheduling, error propagation, and why promise resolution is not the same as immediate fulfillment.

Senior answers should connect promise mechanics to async bugs, retry behavior, cancellation boundaries, and real UI state.

Short Interview Answer

A promise represents the eventual result of an asynchronous operation. It starts pending and eventually settles as fulfilled or rejected. Promise callbacks run as microtasks. The promise resolution procedure determines how a promise adopts another value, promise, or thenable. If a then callback returns a promise or thenable, the next promise waits for it. If it throws, the next promise rejects.

Detailed Interview Answer

A promise has three conceptual states:

pending
fulfilled
rejected

Once a promise settles, its state does not change.

const promise = Promise.resolve("ready");

promise.then((value) => {
  console.log(value);
});

The callback runs later as a microtask, even if the promise is already fulfilled.

Settled vs Resolved

In everyday speech, people often use resolved to mean fulfilled. More precisely, resolution is the process of deciding what a promise should follow.

A promise can resolve to another promise and remain pending until that promise settles.

const slow = new Promise((resolve) => {
  setTimeout(() => resolve("done"), 100);
});

const outer = Promise.resolve(slow);

outer adopts slow. It does not immediately fulfill with the promise object.

Thenable Assimilation

A thenable is any object with a callable then method.

const thenable = {
  then(resolve) {
    resolve("from thenable");
  },
};

Promise.resolve(thenable).then(console.log);

JavaScript promise resolution assimilates thenables so they behave like promises.

This is powerful for interoperability, but it also means suspicious objects with then methods can influence async behavior.

Promise Chaining

Every call to then, catch, or finally returns a new promise.

fetchUser()
  .then((user) => user.id)
  .then((id) => fetchProfile(id))
  .then((profile) => renderProfile(profile));

If a callback returns a plain value, the next promise fulfills with that value.

If a callback returns a promise, the next promise waits for it.

If a callback throws, the next promise rejects.

Error Propagation

Promise chains propagate errors until a rejection handler handles them.

fetchUser()
  .then((user) => {
    if (!user.active) {
      throw new Error("Inactive user");
    }

    return user;
  })
  .catch((error) => {
    console.error(error);
  });

Throwing inside then is equivalent to returning a rejected promise for the next link.

Microtask Scheduling

Promise reactions run in the microtask queue.

console.log("A");

Promise.resolve().then(() => console.log("B"));

console.log("C");

The output is:

A
C
B

The promise callback runs after the current synchronous stack finishes.

Chaining Diagram


finally Behavior

finally runs whether the promise fulfills or rejects.

loadData()
  .finally(() => {
    setLoading(false);
  });

If finally returns a rejected promise or throws, it can replace the outcome with a rejection.

Otherwise, the original value or reason passes through.

Promise.all

Promise.all fulfills when every input fulfills and rejects when the first input rejects.

const [user, settings] = await Promise.all([
  fetchUser(),
  fetchSettings(),
]);

Use it when all results are required.

Promise.allSettled

Promise.allSettled waits for every input and gives the result of each.

const results = await Promise.allSettled(tasks);

Use it when partial failure is acceptable and every result must be inspected.

Promise.race and Promise.any

Promise.race settles with the first settled input.

Promise.any fulfills with the first fulfilled input and rejects only if all inputs reject.

These are useful for timeouts, mirrors, fallback sources, and resilience patterns.

Common Mistakes

A common mistake is forgetting to return a promise from inside then.

doOne().then(() => {
  doTwo();
});

The chain above does not wait for doTwo. Return it or use await.

Another mistake is thinking promises cancel work. Promises represent results; cancellation needs a separate mechanism such as AbortController.

Senior Trade-Offs

Promise mechanics matter when building retry systems, request deduplication, loading states, and error boundaries.

Avoid mixing deeply nested promise chains with async and await in a way that hides ownership of errors.

Code or Design Example

async function loadProfile(userId, signal) {
  const [user, permissions] = await Promise.all([
    fetch(`/api/users/${userId}`, { signal }).then((res) => res.json()),
    fetch(`/api/users/${userId}/permissions`, { signal }).then((res) => res.json()),
  ]);

  return { user, permissions };
}

This runs independent requests concurrently and lets AbortController handle cancellation.

Debugging Workflow

When promise behavior is wrong:

check missing returns in then chains
check whether errors are swallowed
check microtask ordering
check whether a thenable is being assimilated
check whether Promise.all should be allSettled
check whether cancellation needs AbortController
check loading state cleanup in finally

Interview Framing

Define states, settlement, and microtasks first. Then explain that promise resolution adopts promises and thenables, which is why chaining waits for returned async work.

Revision Notes

Promise callbacks run as microtasks. Promise resolution adopts returned promises and thenables. Errors propagate through chains until handled.

Key Takeaways

Promises are a scheduling and result-composition primitive. Senior understanding comes from knowing how values, errors, thenables, and microtasks move through a chain.

Follow-Up Questions

  1. What are promise states?
  2. What does settlement mean?
  3. Why does then run asynchronously?
  4. What is a thenable?
  5. What does promise resolution adopt?
  6. What happens if a then callback throws?
  7. What happens if a then callback returns a promise?
  8. What is the difference between all and allSettled?
  9. Do promises cancel work?
  10. When would you use finally?

How I Would Answer This In A Real Interview

I would explain pending, fulfilled, rejected, and microtasks. Then I would say that promise resolution adopts returned promises or thenables, so chaining waits for async work. I would close with error propagation, Promise.all trade-offs, and cancellation through AbortController.