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

  1. Multiple calls are not currently supported (workaround: Save the callback function from the THEN method into an array)
  2. Chained invocation not supported (solution: Return Promise in then method)
  3. 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

  1. All: The argument is a Promises array that returns a promise

    An array that returns all promise results, reject, after all promise execution succeeds

  2. 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

  1. Race: return a promise, as soon as a promise has results (race)
  2. 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:
  1. Define state
  2. Define the resolve and reject callbacks
  3. Resolve execute microtask queue: change state, get value, then pass execute successful callback
  4. Reject Execute microtask queue: change state, get Reason, then pass execution failure callback
  • Logic for the then method:
  1. Judge onFulfilled, onRejected = this is a pity.
  2. Return Promise resovle/reject to support chained calls,
  3. Check whether the previous promise state is confirmed. If yes, execute onFufilled/onRejected
  4. Push (() => {execute onFulfilled/ onFulfilled})