跟我一步步来手写Promise

40 阅读8分钟

初步实现Promise雏形

  • 实现Promise之前,先看一下promise是如何使用的,再根据它的使用方法,来一步步的实现。首先,Promise就是一个用来存储数据的容器,它拥有着一套特殊的存取数据的方式,这个方式使得它里面可以存储异步调用的结果。创建Promise时,构造函数中需要一个函数作为参数, Promise构造函数的回调函数,他会在创建Promise时调用,调用时会有两个参数传递进去(resolve、reject),resolve 和reject是两个函数,通过这两个函数可以向Promise中存储数据,resolve在执行正常时存储数据,reject在执行错误时存储数据。可以通过Promise的实例方法then来读取Promise中存储的数据,then需要两个回调函数作为参数,回调函数用来获取Promise中的数据,通过resolve存储的数据,会调用第一个函数返回,可以在第一个函数中编写处理数据的代码,通过reject存储的数据或者出现异常时,会调用第二个函数返回可以在第二个函数中编写处理异常的代码。
  • Promise中维护了两个隐藏属性,PromiseResult用来存储数据,PromiseState记录Promise的状态(三种状态)。pending(进行中)fulfilled(完成) 通过resolve存储数据时,rejected(拒绝,出错了) 出错了或者通过reject存储数据时。
  • 当Promise创建时,PromiseState初始值为pending,当通过resolve存储数据时 PromiseState 变为fulfilled(完成),PromiseResult变为存储的数据。 当通过reject存储数据或出错时 PromiseState 变为rejected(拒绝,出错了), PromiseResult变为存储的数据 或 异常对象。
  • 当我们通过then读取数据时,相当于为Promise设置了回调函数,如果PromiseState变为fulfilled,则调用then的第一个回调函数来返回数据,如果PromiseState变为rejected,则调用then的第二个回调函数来返回数据

来看一下我们写的Promise要达到的效果(我们写的Promise叫MyPromise)

const mp = new MyPromise((resolve, reject) => {
    resolve("孙悟空")
})

mp.then((result) => {
  console.log('读取数据', result)
})

初步实现Promise代码

const PROMISE_STATE = {
  PENDING: 0,
  FULFILLED: 1,
  REJECTED: 2
}

class MyPromise {

  // 创建一个变量用来存储Promise的结果
  #result
  // 创建一个变量来记录Promise的状态
  #state = PROMISE_STATE.PENDING // pending 0 fulfilled 1 rejected 2

  constructor(executor) {
    // 接受一个 执行器 作为参数
    executor(this.#resolve.bind(this), this.#reject.bind(this)) //调用回调函数
  }

  // 私有的resolve()用来存储成功的数据
  #resolve (value) {
    // 禁止值被重复修改
    // 如果state不等于0,说明值已经被修改 函数直接返回
    if (this.#state !== PROMISE_STATE.PENDING) return

    this.#result = value
    this.#state = PROMISE_STATE.FULFILLED //数据填充成功
  }

  /* #resolve = () => {
    console.log(this)
  } */

  // 私有的reject()用来存储拒绝的数据
  #reject (reason) {
     if (this.#state !== PROMISE_STATE.PENDING) return
     this.#result = value
     this.#state = PROMISE_STATE.REJECTED
  }

  // 添加一个用来读取数据的then方法
  then (onFulfilled, onRejected) {
    if (this.#state === PROMISE_STATE.FULFILLED) {
      onFulfilled(this.#result)
    }else if(this.#state === PROMISE_STATE.REJECTED){
      onRejected(this.#result)
    }
  }
}

这里resolve和reject前面加的#是为了作为类的私有方法,防止在外面能访问到。另外,#resolve和#reject中的this,如果没有在constructor类的构造函数中给#resolve和#reject用bind绑定this,#resolve和#reject中的this会指向undefined。除了使用bind绑定this以外,还可以用箭头函数来实现#resolve和#reject,效果是一样的。

思考:当前的Promise实现还有什么问题

const mp = new MyPromise((resolve, reject) => {
    setTimeout(() => {
      resolve("孙悟空")
    }, 1000)
})

mp.then((result) => {
  console.log('读取数据', result)
})

执行new MyPromise的时候,如果用一个定时器setTimeout过1秒才给resolve赋值,当mp.then的时候,这个时候this.#state还是PROMISE_STATE.PENDING的状态,这就导致我们会读取不到数据。让我们来思考一下,如何解决这个问题。

解决第一个问题

我们可以创建一个变量来接收一下then的回调函数,当resolve的时候再执行它。

const PROMISE_STATE = {
  PENDING: 0,
  FULFILLED: 1,
  REJECTED: 2
}

class MyPromise {

  // 创建一个变量用来存储Promise的结果
  #result
  // 创建一个变量来记录Promise的状态
  #state = PROMISE_STATE.PENDING // pending 0 fulfilled 1 rejected 2

  // 创建一个变量来存储回调函数
  #callback

  constructor(executor) {
    // 接受一个 执行器 作为参数
    executor(this.#resolve.bind(this), this.#reject.bind(this)) //调用回调函数
  }

  // 私有的resolve()用来存储成功的数据
  #resolve (value) {
    // 禁止值被重复修改
    // 如果state不等于0,说明值已经被修改 函数直接返回
    if (this.#state !== PROMISE_STATE.PENDING) return

    this.#result = value
    this.#state = PROMISE_STATE.FULFILLED //数据填充成功

    // 当resolve执行时,说明数据已经进来了,需要调用then的回调函数
    this.#callback && this.#callback(this.#result)
  }

  /* #resolve = () => {
    console.log(this)
  } */

  // 私有的reject()用来存储拒绝的数据
  #reject (reason) {
     if (this.#state !== PROMISE_STATE.PENDING) return
     this.#result = value
     this.#state = PROMISE_STATE.REJECTED
  }

  // 添加一个用来读取数据的then方法
  then (onFulfilled, onRejected) {
    if (this.#state === PROMISE_STATE.PENDING) {
      // 进入判断说明数据还没有进入Promise,将回调函数设置为callback的值
      this.#callback = onFulfilled
    } else if (this.#state === PROMISE_STATE.FULFILLED) {
      /**
       * 目前来讲,then只能读取已经存储进Promise的数据
       *    而不能读取一部存储的数据
       */
      onFulfilled(this.#result)
    } else if (this.#state === PROMISE_STATE.REJECTED) {
      onRejected(this.#result)
    }
  }
}

思考:当前的Promise实现还有什么问题

const mp = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("孙悟空")
  }, 1000)
})

mp.then((result) => {
  console.log('读取数据1', result)
})

mp.then((result) => {
  console.log('读取数据2', result)
})

mp.then((result) => {
  console.log('读取数据3', result)
})

想一下,上面的代码运行结果会是什么,为什么? 运行结果:读取数据3,孙悟空 因为.then()的时候第三个回调函数覆盖了第一、第二个。

解决第二个问题

既然有可能会有多个.then的回调函数的话,那么接收then的回调函数也得是一个数组callbacks了。另外,之前 都是直接调用的,then的回调函数,应该放入到微任务队列中执行,而不是直接调用。

const PROMISE_STATE = {
  PENDING: 0,
  FULFILLED: 1,
  REJECTED: 2
}

class MyPromise {

  // 创建一个变量用来存储Promise的结果
  #result
  // 创建一个变量来记录Promise的状态
  #state = PROMISE_STATE.PENDING // pending 0 fulfilled 1 rejected 2

  // 创建一个变量来存储回调函数
  // 由于回调函数可能有多个,所以我们使用数组来存储回调函数
  #callbacks = []

  constructor(executor) {
    // 接受一个 执行器 作为参数
    executor(this.#resolve.bind(this), this.#reject.bind(this)) //调用回调函数
  }

  // 私有的resolve()用来存储成功的数据
  #resolve (value) {
    // 禁止值被重复修改
    // 如果state不等于0,说明值已经被修改 函数直接返回
    if (this.#state !== PROMISE_STATE.PENDING) return

    this.#result = value
    this.#state = PROMISE_STATE.FULFILLED //数据填充成功

    // 当resolve执行时,说明数据已经进来了,需要调用then的回调函数
    queueMicrotask(() => {
      // 调用callbacks中的所有函数
      this.#callbacks.forEach(cb => {
        cb()
      })
    })
  }

  /* #resolve = () => {
    console.log(this)
  } */

  // 私有的reject()用来存储拒绝的数据
  #reject (reason) {

  }

  // 添加一个用来读取数据的then方法
  then (onFulfilled, onRejected) {
    if (this.#state === PROMISE_STATE.PENDING) {
      // 进入判断说明数据还没有进入Promise,将回调函数设置为callback的值
      // this.#callback = onFulfilled
      this.#callbacks.push(() => {
        onFulfilled(this.#result)
      })
    } else if (this.#state === PROMISE_STATE.FULFILLED) {
      /**
       * 目前来讲,then只能读取已经存储进Promise的数据
       *    而不能读取一部存储的数据
       */
      // onFulfilled(this.#result)
      /**
       * then的回调函数,应该放入到微任务队列中执行,而不是直接调用
       */
      queueMicrotask(() => {
        onFulfilled(this.#result)
      })
    }
  }
}

思考:看下面的代码,当前的MyPromise可以链式调用吗?

const mp = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("孙悟空")
  }, 1000)
})

let result = mp.then((result) => {
  console.log('读取数据1', result)
  return '猪八戒'
}).then(r => {
  console.log("读取数据2", r)
  return "沙和尚"
}).then(r => {
  console.log("读取数据3", r)
})

解决第三个问题

如何让MyPromise可以链式调用,需要我们使得MyPromise中的then方法的返回值返回一个新的MyPromise,并且在里面用resolve接收一下onFulFilled(this.#result)的返回值。最后代码如下:

const PROMISE_STATE = {
  PENDING: 0,
  FULFILLED: 1,
  REJECTED: 2
}

class MyPromise {

  // 创建一个变量用来存储Promise的结果
  #result
  // 创建一个变量来记录Promise的状态
  #state = PROMISE_STATE.PENDING // pending 0 fulfilled 1 rejected 2

  // 创建一个变量来存储回调函数
  // 由于回调函数可能有多个,所以我们使用数组来存储回调函数
  #callbacks = []

  constructor(executor) {
    // 接受一个 执行器 作为参数
    executor(this.#resolve.bind(this), this.#reject.bind(this)) //调用回调函数
  }

  // 私有的resolve()用来存储成功的数据
  #resolve (value) {
    // 禁止值被重复修改
    // 如果state不等于0,说明值已经被修改 函数直接返回
    if (this.#state !== PROMISE_STATE.PENDING) return

    this.#result = value
    this.#state = PROMISE_STATE.FULFILLED //数据填充成功

    // 当resolve执行时,说明数据已经进来了,需要调用then的回调函数
    queueMicrotask(() => {
      // 调用callbacks中的所有函数
      this.#callbacks.forEach(cb => {
        cb()
      })
    })
  }

  /* #resolve = () => {
    console.log(this)
  } */

  // 私有的reject()用来存储拒绝的数据
  #reject (reason) {

  }

  // 添加一个用来读取数据的then方法
  then (onFulfilled, onRejected) {
    /**
     * 谁将成为then返回的新Promise中的数据???
     */
    return new MyPromise((resolve, reject) => {

      if (this.#state === PROMISE_STATE.PENDING) {
        // 进入判断说明数据还没有进入Promise,将回调函数设置为callback的值
        // this.#callback = onFulfilled
        this.#callbacks.push(() => {
          resolve(onFulfilled(this.#result))
        })
      } else if (this.#state === PROMISE_STATE.FULFILLED) {
        /**
         * 目前来讲,then只能读取已经存储进Promise的数据
         *    而不能读取一部存储的数据
         */
        // onFulfilled(this.#result)
        /**
         * then的回调函数,应该放入到微任务队列中执行,而不是直接调用
         *      then中回调函数的返回值,会成为新的Promise中的数据
         */
        queueMicrotask(() => {
          resolve(onFulfilled(this.#result))
        })
      }
    })
  }
}

以上就是手写Promise的全部内容了。