动手实现promise

387 阅读7分钟

开头

手写了一份通过标准测试的Promise源码,以它为例,详解怎么写出来,细到每一行代码。

Promises/A+

Promise的功能和特性,请参考promise/A+标准。

1


function Promise(executor) {
  this.status = 'pending'; // pending, fulfilled, rejected
  this.value = null;
  this.handlers = [];
  this._doResolve(executor)
}

  • Promise是一个构造函数,接收一个函数作为参数
  • status记录的是Promise的状态
  • value记录的是Promise的值
  • handlers是当前Promise状态变化后要执行的函数,这些函数是通过then方法添加进来。每一项数据结构为:
{
  resolve: onFulfilled,
  reject: onRejected,
  promise: thenPromise,
}

resolve和reject是因为Promise有两种可能的结果,对应不同函数。promise记录的是新的Promise,用于then方法将promise传递下去。

ps. this.handlers能改成对象吗,只有一项,答案是不行的。虽然每次then会返回新的Promise,但是同一个Promise是能添加两次then方法的。比如:


var promise1 = new Promise(function(resolve, reject) {
  resolve(2)
})

promise1.then(function(value) {
  console.log(value + 1)
})

promise1.then(function(value) {
  console.log(value + 2)
})

这种情况下同一个Promise使用了两次的then方法,故handlers需要时数组,才能记录。

  • this._doResolve(executor) 这一行是整个Promise的启动行。因为Promise接收的是一个函数,必须调用它才能开启整个流程。如下图,Promise须自动调用function(resolve, reject){ resolve(2) }这个函数。
new Promise(function(resolve, reject) {
  resolve(2)
})

2

Promise.prototype = {
  constructor: Promise,
  _doResolve: function (fn) {
    var done = false, self = this;
    try {
      fn(function (value) {
        if (done) return
        done = true;
        self.resolve(value)
      }, function (reason) {
        if (done) return
        done = true;
        self.reject(reason)
      })
    } catch (e) {
      if (done) return
      done = true;
      self.reject(e)
    }
  }
}
  • _doResolve的参数fn就是new Promise时传进来的函数
  • 使用done是为了保证这个函数只执行一次
  • 使用try...catch是因为:函数作为参数,由外面传递过来,那么函数是否符合语法无法保证。故「执行作为参数的函数」,都应该添加try...catch。如果错误,Promise状态变成rejected。
  • fn两个参数,也是函数。根据Promise执行结果,启动内部流程。每次新建Promise都需要手动触发。
new Promise(function(resolve, reject) {
  resolve(2)  // 这里就会调用fn函数第一个参数,启动内部的self.resolve(value)流程
})

3

Promise.prototype = {
  ...
  then: function (onFulfilled, onRejected) {
    var self = this;
    var thenPromise = new Promise(function () { });
    if (self.status === 'pending') {
      self.handlers.push({
        promise: thenPromise,
        resolve: onFulfilled,
        reject: onRejected
      })
    } else {
      self._handle(thenPromise, onFulfilled, onRejected);
    }
    return thenPromise
  },
}
  • then必须返回一个新的Promise,故有thenPromise
  • then添加的是接下来执行的函数,有两种可能onFulfilled和onRejected;与thenPromise组成一项添加到数组handlers(将过段时间才执行的函数用数组先储存,到时再取,是一种常用方法)
  • status有两种情况,一种是pending,另外一种是「fulfilled或rejected中一种」。故分情况pending添加到handlers,否则就直接执行。

下面这种情况,第一个then函数执行时,Promise的status是fulfilled,第二个then函数的Promise的status是pending。因为第一个Promise已由resolve(2)将状态改成fulfilled;而第二个Promise添加then函数时,还没有执行resolve(也就是内部执行resolve是异步,目的是为了防止执行顺序出错,后面会讲)。

new Promise(function(resolve, reject) {
  resolve(2)
}).then(function(value) {
  console.log(33, value)
  return 5
}).then(function(value) {
  console.log(44, value)
})

4

Promise.prototype = {
  ...
  resolve: function (result) {
    var self = this;
    if (result === self) {
      throw new TypeError('Promise can not resolved by itself');
    }
    try {
      var then = self._getThen(result);

      if (then) {
        self._doResolve(then.bind(result))
        return
      }
      self.status = 'fulfilled';
      self.value = result;
      self._dequeue();
    } catch (e) {
      self.reject(e)
    }
  },
  reject: function (reason) {
    var self = this;
    self.status = 'rejected';
    self.value = reason;
    self._dequeue()
  },
}

  • resolve和reject都是改变Promise状态和赋予Promise值,然后启动_dequeue(实际就是调用handlers函数)
  • resolve多了两个判断,一是返回的值不能是Promise自己;二是返回的值是Promise,需要特殊处理
  • _getThen方法通过鸭子辨证法判断返回值是否是Promise,如果是返回这个子Promise的then值;

鸭子辨证法是一种证明方法:当一个事物具有鸭子的特性,那我们就认为它是鸭子。当一个对象或函数具有then方法,那么我们就认为它是一个Promise。鸭子辨证法有可能出错,但是绝大部分情况下是可行的。代码如下:

Promise.prototype = {
  ...
  _getThen: function (result) {
    var t = typeof result;
    if (result && (t === 'object' || t === 'function')) {
      var then = result.then;
      if (typeof then === 'function') {
        return then
      }
    }
    return null
  },
}

接下来三行代码,是整个Promise源码最难理解的代码,解决的问题是如果返回的是子Promise,那么该如何继续这个流程:

if (then) {
  self._doResolve(then.bind(result))
  return
}
  • 流程应该return掉,因为子Promise还没有结束,当前Promise状态是不能改变
  • 当前流程应该重新开始,因为原先流程已经被子Promise打断了,这一次self._doResolve的是子Promise的结果
  • 子Promise的结果在哪里呢,在子Promise的then函数中,因为then函数存储的就是Promise的执行结果
  • then.bind(result),将then绑定到子Promise中,确保执行的是子Promise返回的结果;使用bind而不是call或apply是因为bind返回一个新的函数
  • self._doResolve会执行then函数,并将「function (value) { if (done) return; done = true; self.resolve(value) }」当成onFulfilled传递给then

简单来说:当Promise遇到值为子Promise,那么就重新启动Promise,并把子Promise的执行结果(也就是子Promise的then函数)拿过来执行。相当于一条绳子,剪断,再加一段把两边合起来。

5

Promise.prototype = {
  ...
  _dequeue: function () {
    var self = this;
    var handle;

    while (self.handlers.length) {
      handle = self.handlers.shift();
      self._handle(handle.promise, handle.resolve, handle.reject)
    }
  },
  _handle: function (thenPromise, onFulfilled, onRejected) {
    var self = this;
    setTimeout(function () {
      var callback = self.status == 'fulfilled' ? onFulfilled : onRejected
      if (typeof callback === 'function') {
        try {
          const result = callback(self.value)
          thenPromise.resolve(result)
        } catch (e) {
          thenPromise.reject(e)
        }
        return
      }
      self.status == 'fulfilled' ? thenPromise.resolve(self.value) : thenPromise.reject(self.value)
    }, 0)
  },
}

  • _dequeue实际上是批量执行handlers里面函数
  • _handle中,由于then还可以传函数之外的值,故需要判断是否是函数。如下,then(5)也是不会报错的。
new Promise(function(resolve, reject) {
  resolve(2)
}).then(5).then(function(){
  console.log(3)
})

  • thenPromise.resolve(result) 用thenPromise开启下一次循环,此功能类似于上面例子的resolve(2),只是第一次要手动触发,接下来每一个由Promise自动触发。完成链式调用。
  • 使用setTimeout(function(){}, 0),是为了保证then的参数--函数都是异步执行,防止出现函数执行顺序不确定情况:如下图,如果then是同步执行,那么bar和foo谁先执行不确定;但如果是异步执行,那么foo肯定先执行
new Promise.then(function(){ 
  if (true) { 
    // 同步执行 
    bar(); 
  } else { 
    // 异步执行 (如:使用第三方库)
     setTimeout(function(){ 
        bar(); 
     }) 
  } 
}); 

foo();

总结

从源码实现的角度:Promise相当于一个中间层,代理了函数和回调函数。用status表示状态,用value表示值;用handlers存储回调;用_doResolve开启流程,并做成功回调和失败回调之分;用then添加回调函数,返回新的Promise;执行完handlers中函数,又重新开启resolve,形成链式调用。

以下为全部代码:

function Promise(executor) {
  this.status = 'pending';
  this.value = null;
  this.handlers = [];
  this._doResolve(executor)
}

Promise.prototype = {
  constructor: Promise,
  _doResolve: function (fn) {
    var done = false, self = this;
    try {
      fn(function (value) {
        if (done) return
        done = true;
        self.resolve(value)
      }, function (reason) {
        if (done) return
        done = true;
        self.reject(reason)
      })
    } catch (e) {
      if (done) return
      done = true;
      self.reject(e)
    }
  },
  _getThen: function (result) {
    var t = typeof result;
    if (result && (t === 'object' || t === 'function')) {
      var then = result.then;
      if (typeof then === 'function') {
        return then
      }
    }
    return null
  },
  _dequeue: function () {
    var self = this;
    var handle;

    while (self.handlers.length) {
      handle = self.handlers.shift();
      self._handle(handle.promise, handle.resolve, handle.reject)
    }
  },
  _handle: function (thenPromise, onFulfilled, onRejected) {
    var self = this;
    setTimeout(function () {
      var callback = self.status == 'fulfilled' ? onFulfilled : onRejected
      if (typeof callback === 'function') {
        try {
          const result = callback(self.value)
          thenPromise.resolve(result)
        } catch (e) {
          thenPromise.reject(e)
        }
        return
      }
      self.status == 'fulfilled' ? thenPromise.resolve(self.value) : thenPromise.reject(self.value)
    }, 0)
  },
  resolve: function (result) {
    var self = this;
    if (result === self) {
      throw new TypeError('Promise can not resolved by itself');
    }
    try {
      var then = self._getThen(result);

      if (then) {
        self._doResolve(then.bind(result))
        return
      }
      self.status = 'fulfilled';
      self.value = result;
      self._dequeue();
    } catch (e) {
      self.reject(e)
    }
  },
  reject: function (reason) {
    var self = this;
    self.status = 'rejected';
    self.value = reason;
    self._dequeue()
  },
  then: function (onFulfilled, onRejected) {
    var self = this;
    var thenPromise = new Promise(function () { });
    if (self.status === 'pending') {
      self.handlers.push({
        promise: thenPromise,
        resolve: onFulfilled,
        reject: onRejected
      })
    } else {
      self._handle(thenPromise, onFulfilled, onRejected);
    }
    return thenPromise
  },
  all: function (promises) {
    if (!Array.isArray(promises)) {
      throw new TypeError('arguments must be an array')
    }
    return new Promise(function (resolve, reject) {
      let resolvedCounter = 0;
      const promiseNum = promises.length
      let resolvedValues = new Array(promiseNum);
      for (let i = 0; i < promiseNum; i++) { //必须用let 或者在外围加(function(i){})(i),形成闭包
        promises[i].then((value) => {
          resolvedCounter++
          resolvedValues[i] = value
          if (resolvedCounter === promiseNum) {
            return resolve(resolvedValues)
          }
        }, (reason) => {
          return reject(reason)
        })
      }
    })
  },
  race: function (promises) {
    if (!Array.isArray(promises)) {
      throw new TypeError('arguments must be an array')
    }
    return new Promise(function (resolve, reject) {
      for (let i = 0; i < promises.length; i++) {
        promises[i].then((value) => {
          return resolve(value)
        }, (reason) => {
          return reject(reason)
        })
      }
    })
  },
}

Promise.deferred = Promise.defer = function () {
  var dfd = {}
  dfd.promise = new Promise(function (resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

module.exports = Promise;

参考资料