手写 Promise 时遇到的问题总结

130 阅读3分钟

[完整代码地址]

关于 Promise 中异步问题

const fn = () =>
  new Promise((resolve) => {
    setTimeOut(() => {
      resolve(100);
    });
  });

fn().then((res) => console.log(res));
  • 首先明白一点的是 new Promise 中代码是一个同步执行的过程
  • 也就是说在执行 fn()后调用 then() 此时的 setTimeOut 还未执行也就没有进行 resolve,此时的状态任然为 pending
  • 所以此时需要将 then 的方法保存起来等待 resolve 执行的时候进行调用

关于 resolvePromise(promise, x, resolve, reject)定义,主要用于处理 then 链式调用时的各种情况

x 与 promise 相等

  • 如果 promise 和 x 指向同一对象(造成死循环),以 TypeError 为据因拒绝执行 promise
const p = promise.then(function () {
  return p;
});

x 为 Promise

  • 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
  • 如果 x 处于执行态,用相同的值执行 promise
  • 如果 x 处于拒绝态,用相同的据因拒绝 promise

x 为对象或函数

  • 把 x.then 赋值给 then(防止恶意修改 Promise如下)
Object.defineProperty(Promise, "then", {
  get: function () {
    throw Error();
  },
});
  • 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
  • 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise。(then 返回的也可能是 Promise ,需要递归解决)
p1.then((res) => {
  return new Promise((resolve, reject) => {
    resolve(
      new Promise((resolve, reject) => {
        resolve(...);
      })
    );
  });
});
  • 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
  • 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
  • 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用(这里主要针对 thenable,promise 的状态一旦更改就不会再改变)
//thenable可能指如下情况
let thenable = {
  then: function(resolve, reject) {
    resolve({
        then: function(resolve, reject) {
            resolve({
                then: function(resolve, reject) {
                    resolve(42)
                }
            })
        }
    })
  }
};

var promise1 = new Promise((resolve, reject) => {
    resolve(thenable)
})

promise1
.then(function(value) {
    console.log(value)
})
  • 如果 then 不是函数,以 x 为参数执行 promise
  • 如果 x 不为对象或者函数,以 x 为参数执行 promise

完整代码:

    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'

    function MyPromise(executor) {
      this.status = PENDING
      this.value = undefined
      this.reason = undefined;

      this.fulfilledArr = []
      this.rejectedArr = []

      const resolve = (value) => {
        if (this.status === PENDING) {
          this.value = value
          this.status = FULFILLED
          this.fulfilledArr.forEach((cb) => cb())
        }
      }

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

      try {
        executor(resolve, reject)
      } catch (err) {
        reject(err)
      }

    }

    MyPromise.prototype.then = function (onFulfilled, onRejected) {

      onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value
      onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason }

      const promise2 = new MyPromise((resolve, reject) => {

        const fulfilledFN = () => {
          setTimeout(() => {
            try {
              const temp = onFulfilled(this.value)
              resolvePromise(promise2, temp, resolve, reject)
            } catch (err) {
              reject(err)
            }
          })
        }

        const rejectedFN = () => {
          setTimeout(() => {
            try {
              const temp = onRejected(this.reason)
              resolvePromise(promise2, temp, resolve, reject)
            } catch (err) {
              reject(err)
            }
          })
        }

        switch (this.status) {
          case PENDING:
            this.fulfilledArr.push(() => {
              fulfilledFN();
            })

            this.rejectedArr.push(() => {
              rejectedFN();
            })

            break;
          case FULFILLED:
            fulfilledFN()
            break;
          case REJECTED:
            rejectedFN()
            break;
        }
      })
      return promise2
    }


    const resolvePromise = (promise, x, resolve, reject) => {
      if (promise === x) {
        throw new TypeError('循环引用')
      }

      if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
        const then = x.then
        // 防止在thenable对象当中多次调用resolve,reject
        let used = false
        if (typeof then === 'function') {
          then.call(x, y => {
            if (used) return
            used = true
            resolvePromise(promise, y, resolve, reject)
          }, err => {
            if (used) return
            used = true
            reject(err)
          })
        } else {
          resolve(x)
        }
      } else {
        resolve(x)
      }
    }


    MyPromise.resolve = function (value) {
      if (value instanceof MyPromise) {
        return value
      }

      return new MyPromise((resolve) => { resolve(value) })
    }

    MyPromise.reject = function (reason) {
      if (reason instanceof MyPromise) {
        return reason
      }

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

    MyPromise.all = function (args) {
      const len = args.length
      const res = []
      let count = 0
      return new MyPromise((resolve, reject) => {
        for (let i = 0; i < len; i++) {
          try {
            Promise.resolve(args[i]).then((data) => {
              res[i] = data
              if (++count === len) {
                resolve(res)
              }
            })
          } catch (err) {
            reject(err)
          }
        }
      })
    }

    MyPromise.race = function (args) {
      const len = args.length
      return new MyPromise((resolve, reject) => {
        for (let i = 0; i < len; i++) {
          try {
            Promise.resolve(args[i]).then((data) => {
              resolve(data)
            })
          } catch (err) {
            reject(err)
          }
        }
      })
    }