1. Structural design of Promise
class MyPromise {
The /** * Promise specification defines that the constructor accepts an executor callback as an argument. This executor callback accepts two callback functions as arguments: resolve (a callback upon success) and reject (a callback) */
constructor(executor) {
// This is a big pity. // There is a big Promise (pending), which is a pity and rejected (failed).
this.status = 'pending'
this.value = undefined
this.reason = undefined
const resolve = (value) = > {
if(this.status === 'pending') {
// Invoke the resolve state to be fulfilled
this.status = 'fulfilled'
// Save the parameters of the resolve callback
this.value = value
}
}
const reject = (reason) = > {
if(this.status === 'pending') {
// reject changes to Rejected
this.status = 'rejected'
// Save the reject callback argument
this.reason = reason
}
}
// The incoming callback function is executed directly
executor(resolve,reject)
}
}
Copy the code
2. Design of THEN method
class MyPromise {
constructor(executor) {
this.status = 'pending'
this.value = undefined
this.reason = undefined
const resolve = (value) => {
if(this.status === 'pending') {
// Delay calls (microtasks)
queueMicrotask(() => {
this.status = 'fulfilled'
this.value = value
this.onFulfilled && this.onFulfilled(this.value)
}, 0)}}const reject = (reason) => {
if(this.status === 'pending') {
// Delay calls (microtasks)
queueMicrotask(() => {
this.status = 'rejected'
this.reason = reason
this.onRejected && this.onRejected(this.reason)
}, 0)
}
}
executor(resolve,reject)
}
then(onFulfilled, onRejected) {
onFulfilled && this.onFulfilled = onFulfilled
onRejected && this.onRejected = onRejected
}
}
const promise = new MyPromise((resolve, reject) => {
resolve('resolve')
reject('reject')
})
promise.then(res => {
console.log({res})
}, err => {
console.log({err})
})
Copy the code
The then method above has a few points that need to be optimized
- Multiple calls are not currently supported (workaround: Save the callback function from the THEN method into an array)
- Chained invocation not supported (solution: Return Promise in then method)
- If the then method is executed after resolve has already been executed, the current then method cannot be called.
setTimeout(() = > {
promise.then(res= >{
console.log({res})
})
}, 10000)
Copy the code
3. Optimization of THEN method
// Wrap a function
const execFunctionWithCatchError = (exeFn, value, resolve, reject) = > {
try {
const result = exeFn(value)
resolve(result)
} catch(err) {
reject(err)
}
}
class MyPromise {
constructor(executor) {
this.status = 'pending'
this.value = undefined
this.reason = undefined
this.onFulfilledFns = []
this.onRejectFns = []
const resolve = (value) = > {
if(this.status === 'pending') {
queueMicrotask(() = > {
if(this.status ! = ='pending') return
this.status = 'fulfilled'
this.value = value
this.onFulfilledFns.forEach(fn= > {
fn && fn(this.value)
})
}, 0)}}const reject = (reason) = > {
if(this.status === 'pending') {
queueMicrotask(() = > {
if(this.status ! = ='pending') return
this.status = 'rejected'
this.reason = reason
this.onRejectFns.forEach(fn= > {
fn && fn(this.reason)
})
}, 0)}}try {
executor(resolve,reject)
} catch(err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) = > {
// If the state is already determined when calling then, call it directly
if(this.status === 'fulfilled' && onFulfilled) {
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
}
// If the state is already determined when calling then, call it directly
if(this.status === 'rejected' && onRejected) {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
}
if(this.status === 'pending') {
// Put both successful and failed callbacks into the array
if(onFulfilled) this.onFulfilledFns.push(() = > {
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
})
if(onRejected) this.onRejectFns.push(() = > {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
})
}
})
}
}
Copy the code
4. Implementation of catch method
catch(onRejected) {
return this.then(undefined, onRejected)
}
then(onFulfilled, onRejected) {
// Throw an error manually when onRejected is null
onRejected = onRejected || (err= > { throw err })
return new return new MyPromise((resolve, reject) = >{... })}Copy the code
5. Finally method implementation
finally(onFinally) {
Call onFinally regardless of success or failure
this.then(onFinally, onFinally)
}
then(onFulfilled, onRejected) {
// Throw an error manually when onRejected is null
onRejected = onRejected || (err= > { throw err })
// When ondepressing becomes empty, pass on the value of the last promise
onFulfilled = onFulfilled || (value= > value)
return new return new MyPromise((resolve, reject) = >{... })}Copy the code
6. Implement Resolve and Reject
static resolve(value) {
return new MyPromise((resolve) = > resolve(value))
}
static reject(reason) {
return new MyPromise((resolve, reject) = > reject(reason))
}
Copy the code
7. Implementation of all and allSettled
-
All: The argument is a Promises array that returns a promise
An array that returns all promise results, reject, after all promise execution succeeds
-
Return all promise results regardless of promise Rejected
static all(promises) {
return new MyPromise((resolve, reject) = > {
const values = []
promises.forEach(promise= > {
promise.then(res= > {
values.push(res)
if(values.length === promises.length) {
resolve(values)
}
}, err= > {
reject(err)
})
})
})
}
static allSettled(promises) {
return new MyPromise((resolve, reject) = > {
const result = []
promises.forEach(promise= > {
promise.then(res= > {
result.push({state: 'resolved'.value: res})
if(result.length === promises.length) {
resolve(result)
}
}, err= > {
result.push({state: 'rejected'.reason: err})
if(result.length === promises.length) {
resolve(result)
}
})
})
})
}
Copy the code
8. Implementation of race and any
- Race: return a promise, as soon as a promise has results (race)
- Any: must wait until there is a correct result. If there is no correct result, a merged exception is returned
static race(promises) {
return new MyPromise((resolve, reject) = > {
promises.forEach(promise= > {
promise.then(res= > {
resolve(res)
}, err= > {
reject(err)
})
})
})
}
static any(promises) {
return new MyPromise((resolve, reject) = > {
const reasons = []
promises.forEach(promise= > {
promise.then(res= > {
resolve(res)
}, err= > {
reasons.push(err)
if(reasons.length === promises.length) {
reject(reasons)
}
})
})
})
}
Copy the code
9. To summarize
- The logic in the constructor:
- Define state
- Define the resolve and reject callbacks
- Resolve execute microtask queue: change state, get value, then pass execute successful callback
- Reject Execute microtask queue: change state, get Reason, then pass execution failure callback
- Logic for the then method:
- Judge onFulfilled, onRejected = this is a pity.
- Return Promise resovle/reject to support chained calls,
- Check whether the previous promise state is confirmed. If yes, execute onFufilled/onRejected
- Push (() => {execute onFulfilled/ onFulfilled})