手写Promise

69 阅读3分钟

为了加深对Promise的理解,参考网上资源手写了下Promise。还有待通过官方测试用例: github.com/promises-ap…

const PENDING = 'PENDING';      // 进行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失败

function isMyThenable(p) {
  return ((typeof p === 'object' && p !== null) ||
          typeof p === 'function') &&
          typeof p.then === 'function'
}

class MyPromise {
  constructor(exector) {
    this.status = PENDING
    this.value = undefined
    this.reason = undefined

    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []

    const resolve = (value) => {
      if (this.status === PENDING) {
        /**
         * value 如果是个 thenable 或者 promise 实例,则当前正在执行的这个 resolve 所归属的 promise 的状态
         * 跟随 value 的状态
         */
        if (isMyThenable(value)) {
          /**
           * 凡是执行 then 方法的地方都放进任务队列异步处理
           */
          setTimeout(() => {
            try {
              value.then(resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        } else {
          this.status = FULFILLED
          this.value = value
          this.onFulfilledCallbacks.forEach(fn => fn(this.value))
        }
      }
    }

    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }

    try {
      exector(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected:
      reason => { throw new Error(reason instanceof Error ? reason.message : reason) }

    const self = this
    const p2 = new MyPromise((resolve, reject) => {
      const processCallbackReturn = (promise, x, resolve, reject) => {
        if (x === promise) {
          // x 是 promise 实例,抛出异常
          return reject(new TypeError())
        }
        /**
          * 如果 x 是一个 promise 实例,p2 则跟随 x 的状态
          */
        if (x instanceof MyPromise) {
          /**
            * 凡是执行 then 方法的地方都放进任务队列异步处理
            */
          setTimeout(() => {
            x.then((y) => processCallbackReturn(promise, y, resolve, reject), reject)
          })
        } else if (
          typeof x === 'object' ||
          typeof x === 'function'
        ) {
          /**
            * 如果 x 是一个对象或者函数
            */
          let then

          // 如果取 x.then 时候抛异常,则 p2 状态为 REJECTED,且 reason 为 e
          try {
            then = x.then
          } catch (e) {
            return reject(e)
          }

          // 如果 then 是一个函数
          if (typeof then === 'function') {
            let called = false
            /**
              * 此时 p2 的状态取决于 then 方法中如何调用 resolve 和 reject
              * 1. 当 then 内部调用 resolve(y),则重新判断 y 的状态来决定 p2 的状态
              * 2. 当 then 内部调用 reject(r),则 p2 的状态为 REJECTED,且 reason 为 r
              * 3. 当 then 内部同时调用了 resolve 和 reject,或者比如对同一个 y,执行了多次 resolve(y),
              *    则只取第一次执行的 resolve 或者 reject 的结果
              * 4. 当 then 内部执行时抛出异常 e:
              *   1) 如果 resolve 或者 reject 已经调用过,则忽略该异常
              *   2) 否则 p2 的状态为 REJECTED,且 reason 为 e
              *  */ 
            /**
              * 凡是执行 then 方法的地方都放进任务队列异步处理
              */
            setTimeout(() => {
              try {
                then.call(
                  x,
                  y => {
                    if (called) return
                    called = true
                    processCallbackReturn(promise, y, resolve, reject)
                  },
                  r => {
                    if (called) return 
                    called = true
                    reject(r)
                  },
                )
              } catch (e) {
                if (called) return
                reject(e)
              }
            })
          } else {
            // 如果 then 不是一个函数,则 p2 的状态为 FULFILLED,且 value 为 x
            resolve(x)
          }
        } else {
          // 如果 x 不是对象也不是函数,则 p2 的状态为 FULFILLED,且 value 为 x
          resolve(x)
        }
      }

      const resolvePromise = (cb, value) => {
        setTimeout(() => {
          try {
            const x = cb(value)
            processCallbackReturn(p2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }


      if (self.status === PENDING) {
        self.onFulfilledCallbacks.push(() => {
          resolvePromise(onFulfilled, self.value)
        })

        self.onRejectedCallbacks.push(() => {
          resolvePromise(onRejected, self.reason)
        })
      } else if (self.status === FULFILLED) {
        resolvePromise(onFulfilled, self.value)
      } else if (self.status === REJECTED) {
        resolvePromise(onRejected, self.reason)
      }
    })

    return p2
  }

  catch(onRejected) {
    return this.then(null, onRejected)
  }

  static resolve(value) {
    if (value instanceof MyPromise) {
      return value
    } else {
      return new MyPromise((resolve, reject) => resolve(value))
    }
  }

  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason)
    })
  }

  static all(promiseArr) {
    const len = promiseArr.length
    const values = new Array(len)
    let count = 0
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < len; i++) {
        MyPromise.resolve(promiseArr[i]).then(
          val => {
            values[i] = val
            count++
            if (count === len) resolve(values) 
          },
          err => reject(err)
        )
      }
    })
  }

  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      promiseArr.forEach(p => {
        MyPromise.resolve(p).then(
          val => resolve(val),
          err => reject(err),
        )
      })
    })
  }
}

/**
 * test case
 * 
 * out:
 * 
 * 0 1 2 4 3 5 6
 * 
 * 在 node16.16.0 中用 Promise 执行的 结果是
 * 0 1 2 3 4 5 6
 */
MyPromise.resolve().then(() => {
  console.log(0);
  return MyPromise.resolve(4)
}).then(res => {
  console.log(res);
})

MyPromise.resolve().then(() => {
  console.log(1);
}).then(() => {
  console.log(2);
}).then(() => {
  console.log(3);
}).then(() => {
  console.log(5);
}).then(() => {
  console.log(6);
})

/**
 * test case
 * 
 * out
 * 
 * 2 1
 */
new MyPromise((resolve, reject) => {
  MyPromise.resolve().then(() => {
    resolve({
      then: (resolve, reject) => resolve(1)
    });
    MyPromise.resolve().then(() => console.log(2));
  });
}).then(v => console.log(v));