I was reading the PromiseA+ standard last night. It was a great interview question, and when I looked at the JavaScript which was related to the Control Abstraction Object, I thought of Promise. So I tried it myself and tried it by the standards.

The open source community already had A very robust implementation based on Promise A+, but tried to implement one for the sake of learning and research, then ran through the use cases and found that most of the use cases were wrong only after 2.3.3.3.1. Let’s break up the code:

  1. The data structure
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

const isFnc = function isFnc(f) {
    return typeof x === 'function'
}

const isThenable = function isThenable(x) {
  const isFn = isFunc(x)
  constisObject = object ! = =null && typeof object === 'object'
  const hasThenAndIsFun = x.then && typeof isFunc(x.then)

  return (isObject || isFn) && hasThenAndIsFun
}

class $Promise {
    #value = null
    #exception = null
    #reason = null
    #state = PENDING
    #resolveQueue = []
    #rejectQueue = []
    
    constructor(executor) {
      try{
        executor(this.#fulfill, this.#reject)
      } catch(e) {
        this.#reject(e)
      }
    }
    
    #fulfill = (value) = > {}
    
    #reject = (reason) = > {}
    
    #Resolve(promise, x, promiseState){}
    
    then(onFulfilled, onRejected){}}Copy the code
  1. thenPartial code
class $Promise {
    / /...
    then(onFulfilled, onRejected) {
      const promise2State = {}

      const promise2 = new $Promise((res, rej) = > {
        promise2State.res = res
        promise2State.rej = rej
      })

      if (this.#state === FULFILLED) {
        loop(() = > {
          if(isFunc(onFulfilled)) {
            try {
              const x = (1, onFulfilled)(this.#value)
              this.#Resolve(promise2, x, promise2State)
            } catch(e) {
              promise2State.rej(e)
            }
          } else {
            promise2State.res(this.#value)
          }
        })
      }
      
      if (this.#state === REJECTED) {
        loop(() = > {
          if (isFunc(onRejected)) {
            try {
              const x = (1, onRejected)(this.#reason)
              this.#Resolve(promise2, x, promise2State)
            } catch(e) {
              promise2State.rej(e)
            }
          } else {
            promise2State.rej(this.#reason)
          }
        })
      }
      
      if (this.#state === PENDING) {
        this.#resolveQueue.push([promise2, onFulfilled, promise2State])
        this.#rejectQueue.push([promise2, onRejected, promise2State])
      }

      return promise2
    }
    / /...
}
Copy the code
  1. fulfill 和 reject
class $Promise {
    / /...
    #fulfill = (value) = > {

      if (this.#state ! == PENDING)return
      
      this.#value = value
      this.#state = FULFILLED

      let len = this.#resolveQueue.length

      while(len--) {
        loop(() = > {
          const [promise2, onFulfilled, promise2State] = this.#resolveQueue.shift()
          if(isFunc(onFulfilled)) {
            try {
              const x = (1, onFulfilled)(this.#value)
              this.#Resolve(promise2, x, promise2State)
            } catch(e) {
              promise2State.rej(e)
            }
          } else {
            promise2State.res(this.#value)
          }
        })
      }
    }
    
    #reject = (reason) = > {
  
        if (this.#state ! == PENDING)return
        
        this.#reason = reason
        this.#state = REJECTED
  
        let len = this.#rejectQueue.length
  
        while(len--) {
          loop(() = > {

            const [promise2, onRejected, promise2State] = this.#rejectQueue.shift()

            if (isFunc(onRejected)) {
              try {
                const x = (1, onRejected)(this.#reason)
                this.#Resolve(promise2, x)
              } catch(e) {
                promise2State.rej(e)
              }
            } else {
              promise2State.rej(this.#reason)
            }
          })
        }
    }
    / /...
}
Copy the code
  1. Finally,ResolvePart of the code
class $Promise {
    / /...
    #Resolve(promise, x, promiseState){
      if (promise === x) {
        const reason = new TypeError()
        promiseState.rej(reason)
        return
      }

      if (x instanceof $Promise) {
        x.then(promiseState.res, promiseState.rej)
      } else if(isFunc(x) || isObject(x)) {
        let then = null
        try {
          then = x.then
        } catch(e) {
          promiseState.rej(e)
        }

        if (isFunc(then)) {
          let settled = false
          try {
            then.apply(
              x,
              [y= > {
                if(! settled) { settled =true
                  this.#Resolve(promise, y, promiseState)
                }
              },
              r= > {
                if(! settled) { settled =true
                  this.#Resolve(promise, r, promiseState)
                }
              }]
            )
          } catch (e) {
            promiseState.rej(e)
          }
        } else {
          promiseState.res(x)
        }

      } else {
        promiseState.res(x)
      }
    }
    / /...
}
Copy the code

If you really try to implement a Promise according to the standard, you will definitely encounter the following problems:

  1. thenWhen is the argument received by the method called: at the corresponding timePromise çš„Status cashedandOnly platform-specific code remainsWhen, to put it simplystateHas changed and the previous oneMicro tasksAfter the execution. Although it is usedMacro taskThe implementation.
  2. changePromiseWhat are the paths to state: There are several aspects to this questionPromise. The first is created explicitly, the second is created implicitly within the code, and the third is returned as a return value. Although there are manyPromiseIt needs to be handled, but the only thing to remember is that only throughreject.resovleThere are two ways to change the corresponding state.
  3. How is correlation handledPromiseStatus: In fact, the main reason the use case is not finished is not handled wellPromiseThe state of the association between.PromiseThere is aResolved uncashedThe intermediate state of theta is onePromiseThe return value is twoPromiseThe state of is associated, and the association is in a recursive state.
  4. How the error state is displayed:PromiseThe mechanism does not handle both the result and the error of the current result, so it is usually generated by an implicit method to catch the error state during this executionPromiseTo feedback

The above points are the problems I encountered in the implementation process and need to think carefully. After a period of time, I will try my best to complete and solve the current problems, and then record the implementation process in detail