Javascript异步详解(二)-Promise(2)

274 阅读4分钟

1. Promise规范

    上一篇文章我们介绍了Promise范式是如何解决回调函数的控制反转和回调地狱这两个缺点的,并介绍了Promise的主要api,本篇文章我们主要根据这些api的功能来模拟实现它们。在开始实现它们之前,我们首先要了解一下Promise的标准。Promise的标准不是唯一,但是ES6中的Promise使用的是Promise/A+规范,详细内容可以参考:Promise/A+规范

    解读下规范我们可以总结出以下几点内容:

    1. 一个Promise的初始状态位pedding,他可以由此转换成为fulfilled状态或者rejected状态,并且状态一旦改变就无法再改变到其它状态。

    2. 规范里只阐述了then这个api的规范,then方法接收两个可选的参数作为状态改变时的回调,并且返回一个promise。

2. 构造函数实现

    首先我们来实现promise的构造函数,它接收一个立即执行的函数executor,executor会自动的部署两个函数:resolve和reject。所以我们初步的构造函数是这样的:

function Promise(executor) {
    this.status = 'pedding';
    
  try { 
    executor(resolve, reject) 
  } catch(e) {
    reject(e)
  }}

    然后我们需要部署resolve和reject这两个函数,并且添加两个数组用来存放回调函数:

function Promise(executor) {
  this.status = 'pedding';   
  self.onResolvedCallback = [];
  self.onRejectedCallback = [];

  function resolve(value) {
    // TODO
  }  function reject(reason) {
    // TODO
  }  try { 
    executor(resolve, reject) 
  } catch(e) {
    reject(e)
  }
}

    接下来我们来实现resolve和reject两个函数,这两个函数的作用是将Promise的状态改变并且调用注册的回调函数

function Promise(executor) {
  this.status = 'pedding';
  this.data = undefined;
  self.onResolvedCallback = [];
  self.onRejectedCallback = [];

  function resolve(value) {
    if (self.status === 'pending') {
        self.status = 'resolved';
        self.data = value;
        for(var i = 0; i < self.onResolvedCallback.length; i++) {
            self.onResolvedCallback[i](value)
        }
     }  }  

  function reject(reason) {
    if(self.status === 'pending') {
      self.status = 'rejected';
      self.data = reason;
      for(var i = 0; i < self.onRejectedCallback.length; i++) {
        self.onRejectedCallback[i](reason);
      }
    }  }  

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

3. then方法实现

    Promise的构造函数基本功能已经实现,下面我们来实现promise.then方法。then方法接收两个函数分别处理成功和失败两种状态的回掉函数。需要注意的是,根据Promise标准,如果参数不是fuction的话需要忽略。同时,then方法会返回一个新的Promise以实现链式调用。如果状态为resolve或reject,会执行对应的回调函数并判断回调函数的返回类型,如果是Promise的话会直接取它的结果作为Promise2的结果,否则用返回值作为Promise2的结果。

    如果状态为pedding的话,说明异步函数还没有结果返回,这时将resolve和reject的逻辑作为回掉函数注册到回调函数队列里,并返回Promise2

Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2

  onResolved = typeof onResolved === 'function' ? onResolved : function(v) {}
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {}

  if (self.status === 'resolved') {
    return promise2 = new Promise(function(resolve, reject) {
        try {        var x = onResolved(self.data)
        if (x instanceof Promise) { 
          x.then(resolve, reject)
        }
        resolve(x) 
      } catch (e) {
        reject(e) 
      }    })
  }

  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
      try {        var x = onRejected(self.data)
        if (x instanceof Promise) {
          x.then(resolve, reject)
        }
      } catch (e) {
        reject(e)
      }    })
  }

  if (self.status === 'pending') {
    return promise2 = new Promise(function(resolve, reject) {
        self.onResolvedCallback.push(function(value) {        try {
          var x = onResolved(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })

      self.onRejectedCallback.push(function(reason) {
        try {
          var x = onRejected(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}

Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}

4. Promise其它api

至此我们基本实现了Promise/A+规范中的Promise范式,并且根据then实现了catch方法。下面我们根据Promise的基础来实现Promise其它常用的api。

4.1 Promise.all 

Promise.all 接收一个Promise的数组作为参数,执行所有的Promise,返回一个新的Promise,并将每个Promise的返回值推入存储数组,最后通过调用resolve函数使用户能在then方法中完成回调内容。

Promise.prototype.all = function(promises) {
   return new Promise(function(resolve, reject) {
      var resolvedCounter = 0
      var promiseNum = promises.length
      var resolvedValues = new Array(promiseNum)
      for (var i = 0; i < promiseNum; i++) {
         (function(i) {
            Promise.resolve(promises[i]).then(function(value) {
               resolvedCounter++
               resolvedValues[i] = value
               if (resolvedCounter == promiseNum) {
                  return resolve(resolvedValues)
               }
            }, function(reason) {
               return reject(reason)
            })
          })(i)
     }
  })
}

4.2 Promise.race

race函数同样接收一个Promise数组,执行里面所有的Promise,并返回新的Promise,当有一个Promise状态变为resolve或reject之后,直接调用resolve或reject函数。

Promise.prototype.race = function(promises) {
  return new Promise(function(resolve, reject) {
     for (var i = 0; i < promises.length; i++) {
        Promise.resolve(promises[i]).then(function(value) {
           return resolve(value)
        }, function(reason) {
           return reject(reason)
        })
      }
   })
}

5.总结

本系列文章属于JavaScript异步系列中的一个子系列,主要讲述了Promise范式如何通过中间层来解决传统回调函数模式的控制反转和回调地狱问题,并介绍了Promise的使用和其主要api,最后我们通过原生的js来模拟实现了ES6中Promise的几个核心方法。下一个系列我们将继续探索JavaScript的异步模式,生成器和async函数。