亲手实现一个Promise

GD Star Rating
loading...

为何要手写Promise?直接import不行吗?

身处技术圈,前端技术真的是日新月异,异步的实现方式从callback、到Promise、再到Generator、Async/Await,有了长足的发展,Promise作为发展过程中的一种产物其既是callback的一种改良,也是Generator、Async/Await的基础。

直接import一个Promise类库当然可行(在支持的浏览器中直接使用Promise也未尝不可),但是如果我们能够亲手实现一个Promise,那么我们还能够:

  • 对Promise的理解更加深刻
  • 提升自己的类库编写能力,尤其是各种边界值的处理

另外有一点需要说明,本文针对的是有一定Promise基础的同学,基础知识不在本文范畴之内,不了解的话请自行Google :)

实现的内容

实现之前我们先来思考下,一个Promise中到底有哪些是必须要实现的?

按照Promise/A+的标准来说,只需要Promise的then方法即可,至于怎么实现、constructor怎么写,完全没有提及。不过虽然看似东西很少,但是实质上还是有蛮多东西要处理的。

首先,constructor是必须的,在这里我们需要做一些初始化的动作:

  • 初始状态处理
  • 对传入的resolver进行判断并做容错处理
  • 执行传入的resolver函数
  • 定义resolve、reject函数

其次,then方法由于是每个实例上都有,所以会挂在prototype上面,then方法中我们需要做的事情有

  • 对传入的onFulfilled、onRejected进行判断并做容错处理
  • 判断当前Promise的状态并返回一个新的Promise

然后,我们还需要一个resolvePromise函数,这个函数根据标准而来(其实不管怎样,Promise的实现中都是少不了这样的处理过程)

至于原型上的catch方法以及race、all等静态方法,暂时不处理,而且这些在我们实现了Promise的基础代码后都是很容易的事情,暂时不做处理。

Constructor

function Promise(resolver) {

  var self = this
  self.status = 'pending'
  self.fulfilledCallbacks = []
  self.rejectedCallbacks = []
  self.data = null

  if(typeof resolver !== 'function') {
    throw new TypeError('Promise resolver ' + resolver + 'is not a function')
  }

  function onFulfilled(data) {
    setTimeout(function () {
      if(self.status !== 'pending') return
      self.status = 'fulfilled'
      self.data = data
      var currentCallback

      for(var i = 0; i < self.fulfilledCallbacks.length; i++) {
        currentCallback = self.fulfilledCallbacks[i]
        typeof currentCallback === 'function' && currentCallback(self.data)
      }
    })
  }

  function onRejected(reason) {
    setTimeout(function () {
      if(self.status !== 'pending') return
      self.status = 'rejected'
      self.data = reason
      var currentCallback
      for(var i = 0; i < self.rejectedCallbacks.length; i++) {
        currentCallback = self.rejectedCallbacks[i]
        typeof currentCallback === 'function' && currentCallback(self.data)
      }
    })
  }

  try {
    resolver(onFulfilled, onRejected)
  }catch (e){
    onRejected(e)
  }

}

可以看到,在构造函数中最上面是对初始状态的处理。

self.status = 'pending' // 初始化时必定是pengding状态,
self.fulfilledCallbacks = []  // fulfilled后执行的回调
self.rejectedCallbacks = []  // rejected后执行的回调
self.data = null  // 初始化时Promise内部的数据

接着对resolver做了判断,

if(typeof resolver !== 'function') {
  throw new TypeError('Promise resolver ' + resolver + 'is not a function')
}

因为对于一个Promise来说,传入的resolver不是function就没有意义了,比如

var p = new Promise(100) //那么这里传入100的时候,到底想干嘛?

然后是onFulfilled和onRejected这两个函数的定义,即我们传递给一个Promise实例里的resolve和reject两个参数

var p = new Promise( (resolve, reject) => {
  resolve() // resolve即onFulfilled、reject即onRejected
})
function onFulfilled(data) {
    setTimeout(function () {
      if(self.status !== 'pending') return
      self.status = 'fulfilled'
      self.data = data
      var currentCallback

      for(var i = 0; i < self.fulfilledCallbacks.length; i++) {
        currentCallback = self.fulfilledCallbacks[i]
        typeof currentCallback === 'function' && currentCallback(self.data)
      }
    })
  }

在onFulfilled中首先我们看到的是一个setTimeout,这样做的目的是为了确保onFulfilled是异步执行的,至于为何要异步执行,目前还没有搞懂,个人感觉是非必须的,但是如果设置为同步的会导致测试无法通过,在查看bluebird的源代码时发现这部分根据data做了判断,如果非Promise会是同步,而是Promise的话会异步执行,这个坑先留着,待后面解决。

setTimeout内先对状态进行判断,如果其已经是fulfilled或rejected我们直接返回,接着就是一个for循环执行回调。

onRejected和onFulfilled大同小异,不多赘述。

try {
  resolver(onFulfilled, onRejected)
}catch (e){
  onRejected(e)
}

最后我们直接在一个try/catch中执行传入的resolver,以便对执行resolver时候的错误进行处理。

Promise.prototype.then

Promise.prototype.then = function (onFulfilled, onRejected) {
  var self = this
  var p
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(val){return val}
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason){throw reason}

  if(self.status !== 'pending') {
    return p = new Promise(function (resolve, reject) {
      setTimeout(function () {
        var functionToCall = self.status === 'fulfilled' ? onFulfilled : onRejected
        var result
        try {
          result = functionToCall(self.data)
          resolvePromise(p, result, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  } else {
    return p = new Promise(function (resolve, reject) {
      self.fulfilledCallbacks.push(function(){
        try {
          var result = onFulfilled(self.data)
          // 如果上个then的返回值是个Promise实例 或者Promise resolver里面resolve的结果是个Promise实例
          resolvePromise(p, result, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
      self.rejectedCallbacks.push(function(){
        try {
          var result = onRejected(self.data)
          // 如果上个then的返回值是个Promise实例 或者Promise resolver里面resolve的结果是个Promise实例
          resolvePromise(p, result, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}

根据标准Promise.prototype.then的返回值是一个新的Promise,所以我们可以看到2处类似这样的代码

return p = new Promise(function (resolve, reject) {
  ...
})

这2处就是根据当前Promise实例(self)的状态来做不同的处理

首先看非pending状态

  • 非pending,即self已经被resolved/rejected
  • 这个时候我们需要”马上”对p做处理,而不是将其放入回调中,另外为了确保then中代码块异步执行,相关代码都被包在了一个setTimeout中
  • result即是执行完onFulfilled后的结果,由于并不知道result会是什么样的结果,我们在单独的一个resolvePromise函数中处理,根据result来确定p

接着再看pending,由于self还是pending状态,那么就等到self resolve或者是reject的时候再来处理,即将相应的代码放入fulfilledCallbacks/rejectedCallbacks中即可

resolvePromise

function resolvePromise(promise, x, resolve, reject){
  var then
  var thenCalled

  if(promise === x) reject(new TypeError('Chaining cycle detected for promise!'))

  if(x instanceof Promise) {
    if(x.status === 'pending') {
      x.then(function (value) {
        resolvePromise(promise, value, resolve, reject)
      }, reject)
    }else{
      x.then(resolve,reject)
    }

  } else if(x !== null && (typeof x === 'function' || typeof x === 'object') ){

    try{
      then = x.then
      if(typeof then === 'function') {
        then.call(x, function(y){
          if(thenCalled) return
          thenCalled = true
          resolvePromise(promise, y, resolve, reject)
        }, function(r){
          if(thenCalled) return
          thenCalled = true
          return reject(r)
        })
      } else {
        resolve(x)
      }
    }catch(e){
      if(thenCalled) return
      thenCalled = true
      return reject(e)
    }

  }else{
    resolve(x)
  }

}

刚开始的时候不是很立即为什么会有这么一个过程,想着直接根据then中onFulfilled返回的值(result)做判断,然后返回不同的Promise即可,后来发现result有各种不同的情况,而且不止一个地方需要用到,那么将其抽取出来是就合情合理了。

resolvePromise中主要的处理就是根据传入的x来决定promise(输入参数中的promise)的状态,其中最主要的2块

  • x instanceof Promise 这个主要是判断如果是我们自己创建的Promise的实例
  • x !== null && (typeof x === ‘function’ || typeof x === ‘object’) 这个尝试将x当做thenable处理,确保我们的Promise能够与其他Promise类库交互

其他代码更多的是边界值的判断,这里就不做赘述了。

总结

之前面试的时候被问到怎么实现一个Promise,一脸懵逼,这个问题压根没想过,然后临阵发挥的时候一直纠结于resolve在哪里,现在想想真是傻,为何为停在这里。。。🙃

参考资料

亲手实现一个Promise, 4.5 out of 5 based on 4 ratings

发表评论