Detailed explanation
A Promise in JavaScript is an object representing an eventual outcome, commonly from an asynchronous operation such as a network request or timer. Rather than receiving the result immediately, you attach handlers using .then(), .catch(), and .finally(), or consume the promise using await. Promises make dependent operations easier to compose and give failures a consistent propagation mechanism. They do not themselves create threads or make synchronous work non-blocking.
A promise has three states: pending, fulfilled, and rejected. Pending means no final outcome is available yet; fulfilled means it has a success value; rejected means it has a failure reason, preferably an Error object. Fulfilled and rejected promises are collectively called settled, and their state cannot change again. Importantly, resolved is not an exact synonym for fulfilled: resolving with another pending promise locks in that promise’s eventual outcome while the outer promise remains pending. Once resolution is committed, subsequent calls to resolve or reject cannot override it.
The executor passed to new Promise runs synchronously, but handlers registered through .then(), .catch(), and .finally() run asynchronously through the microtask queue. Even handlers attached to an already-settled promise wait until the current synchronous execution finishes. For example, console.log('sync') runs before a handler registered with Promise.resolve().then(...). In the usual same-turn example, that handler also runs before a zero-delay timer callback. Interviewers often use this ordering to check whether you distinguish promise creation from reaction execution.
Each .then() returns a new promise: returning a plain value fulfills it, returning a promise makes it adopt that outcome, and throwing rejects it. .catch(fn) is equivalent to .then(undefined, fn); returning normally from it recovers the chain. .finally() supports cleanup and normally preserves the original outcome, unless it throws or returns a rejecting promise. An async function always returns a promise, and await turns a rejection into a thrown exception at the suspension point. Handle failures with an appropriate rejection handler or try/catch around await, rather than assuming every chain must end with .catch().
Key points to remember
- The three states are pending, fulfilled, and rejected; fulfilled and rejected mean settled.
- Settlement is irreversible, but resolving with a pending promise does not immediately fulfill it.
- A Promise executor runs synchronously; registered reaction handlers run as microtasks.
- .then() creates a new promise whose outcome depends on the handler’s return value or thrown exception.
- .catch() handles rejection, .finally() performs cleanup, and async/await consumes the same promise mechanism.
Common mistakes candidates make
- Treating resolved and fulfilled as interchangeable, ignoring adoption of another pending promise.
- Assuming the Promise constructor defers its executor or moves expensive computation to another thread.
- Forgetting to return a nested promise from a handler, breaking sequencing and rejection propagation.
Likely follow-up questions
How do Promise.all(), Promise.allSettled(), Promise.race(), and Promise.any() differ?
Promise.all() fulfills with ordered results when all inputs fulfill, or rejects on the first observed rejection; Promise.allSettled() waits for every input outcome and returns status records. Promise.race() adopts the first settlement, whereas Promise.any() takes the first fulfillment and rejects with AggregateError if every input rejects. These describe normal iterable inputs; iteration errors can also reject the returned promises.
Does .finally() change the result of a promise chain?
Normally, it preserves the original value or rejection reason, and its callback receives neither. A thrown exception or returned rejecting promise replaces the original outcome with a rejection; a returned pending promise delays completion.
What happens if a promise rejection is not handled?
Browsers can report it through the unhandledrejection event; Node.js has its own unhandledRejection mechanism and runtime-dependent behaviour. Attach a rejection handler or catch the error around await at the layer responsible for recovery or reporting.
Interview tip: Start with the definition and three states, then demonstrate irreversible settlement and microtask ordering with a small example.
