The Promise Lifecycle
Introduction
A Promise is a state machine for a future value. It starts pending, then settles exactly once as fulfilled with a value or rejected with a reason. Once settled, the result is immutable and every observer sees the same outcome.
The interview-grade mental model separates four moments: executor execution, settlement, thenable adoption, and reaction callbacks running later as microtasks.
Why This Matters
Most promise output puzzles become straightforward once you know that the executor runs synchronously, .then callbacks run as microtasks, resolve can adopt another promise or thenable, and only the first settlement attempt counts.
Theory
States and settlement
A promise has three observable states: pending, fulfilled, and rejected. Fulfilled and rejected are the two settled states. Settlement is one-way: the first effective resolve or reject wins, and later calls are ignored.
Executor now, reactions later
The executor passed to new Promise runs immediately and synchronously. Callback functions registered with .then, .catch, and .finally are promise reactions, so they run later as microtasks after the current call stack is empty.
Resolved is not always fulfilled
resolve(value) fulfils with ordinary values, but if value is a promise or thenable, the outer promise adopts that object's eventual outcome. A promise can therefore be resolved to a pending promise and still remain pending until the inner promise settles.
| Term | Precise meaning |
|---|---|
| Pending | No final result yet. |
| Fulfilled | Settled successfully with a value. |
| Rejected | Settled unsuccessfully with a reason. |
| Settled | Fulfilled or rejected. |
| Resolved | Locked to a value, promise, or thenable; may still be pending. |
Thenable assimilation
A thenable is any object with a callable then property. Promise resolution calls that method and adopts the outcome, while still protecting settle-once semantics. Badly behaved thenables that call both callbacks cannot change the result after the first call.
Throws in the executor
If the executor throws before settlement, the promise rejects with that thrown value. If it throws after the promise has already settled, the throw is ignored for that promise.
Output-puzzle checklist
- Run all synchronous code, including promise executors.
- Note which promises settle and which reactions are queued.
- Drain microtasks after the stack empties.
- Run timers and other macrotasks only after microtasks.
Visual Diagrams
resolve(value)
+----------------+
| v
[ pending ] --+----------> [ fulfilled ]
|
| reject(reason) or executor throw
v
[ rejected ]
fulfilled and rejected are settled states.
After settlement, later resolve or reject calls are ignored.A promise transitions out of pending exactly once.
new Promise(executor)
|
v
executor runs synchronously
|
v
promise settles or remains pending
|
v
then and catch reactions run as microtasks after the stack is emptyConstruction is synchronous; observation is asynchronous.
Code Examples
Settle-once semantics
Only the first settlement attempt matters. Later calls are ignored.
Resolving to a thenable
Resolving with a thenable adopts the thenable's eventual outcome instead of storing the object as-is.
Playground
Press Run to execute the code and see output here.
Output Prediction
Predict the output #1
1console.log('A');2 3const promise = new Promise(function (resolve) {4 console.log('B');5 resolve('C');6 console.log('D');7});8 9promise.then(function (value) {10 console.log(value);11});12 13console.log('E');Predict the output #2
1const promise = new Promise(function (resolve, reject) {2 resolve('first');3 reject('second');4 resolve('third');5});6 7promise.then(8 function (value) {9 console.log(value);10 },11 function (reason) {12 console.log(reason);13 }14);Coding Exercises
Promisify an error-first callback
MediumImplement promisify(fn). The wrapped function should return a promise. The original fn receives a final callback shaped like callback(error, value); reject for a truthy error and fulfil with the value otherwise.
Interview Questions
1What are the states of a promise, and can a settled promise change state?
A promise starts pending, then settles exactly once as fulfilled or rejected. Fulfilled and rejected are final states; once settled, the result cannot change and later calls to resolve or reject are ignored.
Follow-ups
- What is the difference between resolved and fulfilled?
- When do `.then` callbacks run?
2What happens if a promise is resolved with another promise or thenable?
The outer promise adopts the inner promise or thenable's eventual outcome. It fulfils if the adopted value fulfils and rejects if the adopted value rejects. If the adopted promise is still pending, the outer promise is resolved but not yet fulfilled.
Quiz
1. Which statement about a promise executor is true?
2. A promise can be resolved but not fulfilled when it is resolved with...
Summary
- Promises start pending and settle exactly once as fulfilled or rejected.
- The executor runs synchronously; `.then`, `.catch`, and `.finally` callbacks run as microtasks.
- `resolve` adopts promises and thenables, so resolved and fulfilled are not always the same moment.
- After a promise settles, later resolve or reject attempts are ignored.
Cheat Sheet
States: pending → fulfilled or rejected. Fulfilled/rejected = settled.
Settle once: first resolve or reject wins.
Executor: runs synchronously.
Reactions: .then, .catch, .finally run as microtasks.
Resolve vs fulfil: resolve(thenable) adopts the thenable and may remain pending.