EN/CH Mode
BRUCE_FE JS Interview Notes - Hand-written Promise Implementation
Learn how to implement JavaScript Promise from scratch, understand asynchronous processing principles, master Promise A+ specification, and enhance frontend interview competitiveness.
EN/CH Mode
Lazy to read articles? Then watch videos!
Promise Overview
Promise is the standard way to handle asynchronous operations in JavaScript, representing an operation that is not yet completed but will eventually complete. In interviews, hand-writing Promise implementation is a common topic to test understanding of asynchronous processing, event loops, and Promise A+ specification.
Three States of Promise
- 1.
pending
: Initial state, neither success nor failure - 2.
fulfilled
: Operation successfully completed with a result value - 3.
rejected
: Operation failed with an error reason
Promise state transitions can only be:
- 1. pending → fulfilled
- 2. pending → rejected
Once a Promise state changes, it cannot be changed again. This characteristic is called 'immutability'.
Basic Promise Implementation
Below is a simplified Promise implementation with basic functionality:
class MyPromise {
constructor(executor) {
this.state = 'pending' // Initial state
this.value = undefined // Value when successful
this.reason = undefined // Reason when failed
this.onFulfilledCbs = [] // Array to store success callback functions
this.onRejectedCbs = [] // Array to store failure callback functions
// Success handling function
const resolve = (value) => {
if (this.state === 'pending') {
queueMicrotask(() => { // Ensure asynchronous execution
this.state = 'fulfilled'
this.value = value
this.onFulfilledCbs.forEach(fn => fn()) // Execute all registered success callbacks
})
}
}
// Failure handling function
const reject = (reason) => {
if (this.state === 'pending') {
queueMicrotask(() => { // Ensure asynchronous execution
this.state = 'rejected'
this.reason = reason
this.onRejectedCbs.forEach(fn => fn()) // Execute all registered failure callbacks
})
}
}
// Immediately execute the passed function, catch possible errors
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
// Core method for chaining calls
then(onFulfilled, onRejected) {
// Parameter processing, ensure onFulfilled and onRejected are functions
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }
// Return new Promise to implement chaining
return new MyPromise((resolve, reject) => {
// Unified callback logic handling
const handle = (callback, val, resolver, rejecter) => {
queueMicrotask(() => {
try {
const result = callback(val)
// Handle case where callback returns a Promise
result instanceof MyPromise
? result.then(resolver, rejecter)
: resolver(result)
} catch (err) {
rejecter(err)
}
})
}
// Decide how to handle based on current Promise state
if (this.state === 'fulfilled') {
handle(onFulfilled, this.value, resolve, reject)
} else if (this.state === 'rejected') {
handle(onRejected, this.reason, resolve, reject)
} else {
// Promise is still in pending state, store callbacks in corresponding arrays
this.onFulfilledCbs.push(() => {
handle(onFulfilled, this.value, resolve, reject)
})
this.onRejectedCbs.push(() => {
handle(onRejected, this.reason, resolve, reject)
})
}
})
}
}