Core Principles of Promise
Promise State Management Mechanism
The core of a Promise lies in the management of its three immutable states:
- Pending: Initial state, neither fulfilled nor rejected.
- Fulfilled: Operation completed successfully.
- Rejected: Operation failed.
State Transition Rules:
- Pending → Fulfilled (can only transition once).
- Pending → Rejected (can only transition once).
- Fulfilled/Rejected states are immutable.
// Simplified state management implementation
class MyPromise {
constructor(executor) {
this.state = 'pending'; // Initial state
this.value = undefined; // Value on success
this.reason = undefined; // Reason for failure
this.onFulfilledCallbacks = []; // Success callback queue
this.onRejectedCallbacks = []; // Failure callback queue
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
// Execute all success callbacks
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
// Execute all failure callbacks
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject); // Execute executor immediately
} catch (err) {
reject(err); // Reject if executor throws an error
}
}
}Implementation of then Method for Chaining
The then method is central to Promise, enabling chaining and asynchronous execution:
class MyPromise {
// ... Previous state management code
then(onFulfilled, onRejected) {
// Handle value passthrough
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
// Execute onFulfilled asynchronously
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject); // Handle return value
} catch (e) {
reject(e);
}
}, 0);
} else if (this.state === 'rejected') {
// Execute onRejected asynchronously
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else if (this.state === 'pending') {
// If state is undetermined, store callbacks in queue
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2; // Return new Promise for chaining
}
}
// Handle thenable objects and Promise return values
function resolvePromise(promise2, x, resolve, reject) {
// Prevent circular references
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
// Prevent multiple calls
let called = false;
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then;
if (typeof then === 'function') {
// If x is a Promise or thenable object
then.call(
x,
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject); // Recursively resolve
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
// Ordinary object/value
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
// Primitive value
resolve(x);
}
}Asynchronous Execution and Microtask Queue
Promise callback execution follows the Microtask mechanism:
- Difference Between Microtasks and Macrotasks:
- Microtasks: Promise callbacks, MutationObserver, process.nextTick (Node.js).
- Macrotasks: setTimeout, setInterval, I/O operations, UI rendering.
- Implementing a Microtask Queue:
// Simple microtask queue implementation
const microTaskQueue = [];
let isFlushing = false;
function flushMicroTasks() {
if (isFlushing) return;
isFlushing = true;
while (microTaskQueue.length) {
const task = microTaskQueue.shift();
try {
task();
} catch (e) {
console.error('Microtask error:', e);
}
}
isFlushing = false;
}
// Simulate Promise asynchronous execution
function asyncExecute(fn) {
microTaskQueue.push(fn);
// In browsers, a more efficient microtask API is used
// Here, setTimeout is used for simulation (actually a macrotask)
setTimeout(flushMicroTasks, 0);
}
// Usage in then method
if (this.state === 'fulfilled') {
asyncExecute(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}Microtask APIs in Actual Browsers:
- Modern browsers use microtask queues for Promise callbacks.
- Node.js uses process.nextTick and microtask queues.
- The
queueMicrotaskAPI allows direct microtask usage.
Promise.resolve and Promise.reject
These static methods provide shortcuts for creating resolved/rejected Promises:
class MyPromise {
// ... Previous code
static resolve(value) {
// If value is already a Promise, return it
if (value instanceof MyPromise) {
return value;
}
// Otherwise, create a new resolved Promise
return new MyPromise(resolve => {
resolve(value);
});
}
static reject(reason) {
// Create a new rejected Promise
return new MyPromise((_, reject) => {
reject(reason);
});
}
// Handle thenable objects
static resolve(value) {
if (value && typeof value === 'object' && typeof value.then === 'function') {
return new MyPromise((resolve, reject) => {
value.then(resolve, reject);
});
}
return new MyPromise(resolve => resolve(value));
}
}Promise Error Handling Mechanism
Promises provide robust error handling:
- Executor Error Handling:
new Promise((resolve, reject) => {
throw new Error('executor error'); // Automatically caught and rejected
}).catch(err => console.log(err)); // Catch error- Error Handling in
thenMethod:
new Promise((resolve) => {
resolve(1);
})
.then(
value => { throw new Error('then error') }, // Error in success callback
err => console.log('This won’t run') // Won’t catch the above error
)
.catch(err => console.log(err)); // Catches all errors- Error Propagation Mechanism:
new Promise((resolve) => {
resolve(1);
})
.then(value => {
console.log(value);
return 2;
})
.then(value => {
throw new Error('chain error');
})
.then(value => {
console.log('This won’t run');
})
.catch(err => console.log(err)); // Catches any error in the chain- Implementation of
finallyMethod:
class MyPromise {
// ... Previous code
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason; })
);
}
}



