实现基本的Promise

177 阅读6分钟

参考链接

史上最易读懂的 Promise/A+ 完全实现
30分钟,让你彻底明白Promise原理
深入理解 Promise (中)


这篇文章主要讲代码实现以及个人理解,原理部分可以参照上面链接。

基础结构及注意事项

function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise当前的状态
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
  self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面

  executor(resolve, reject) // 执行executor并传入相应的参数
}

到这里,我们实现了Promise的基本雏形。从上面的构造方法中可以看出,我们传入的excutornew Promise()时就会被执行。
那我们来看下面这个场景:

function getUserId() {
    return new Promise(function(resolve) {
        //异步请求
        http.get(url, function(results) {
            resolve(results.id)
        })
    })
}

getUserId().then(function(id) {
    console.log(id)
    //一些处理
})

在这个例子中,http.get(url, function(results) {resolve(results.id)})new Promise时就被执行了,里面的回调函数function(results) {resolve(results.id)}被放入事件循环队列中,等待主线程空闲时执行。注意哟,这个任务是不会被添加到onResolvedCallback/onRejectedCallback队列哟。

实现resolvereject方法

resolvereject方法做的事情很简单,修改promise状态,然后分别将onResolvedCallback/onRejectedCallback的函数全部遍历并执行一遍。

function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise当前的状态
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
  self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面

  executor(resolve, reject) // 执行executor并传入相应的参数
  
  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)
      }
    }
  }
}

实现then方法

then方法的工作其实也很简单,就是注册回调函数,并返回一个新的Promise对象. 我们思考这样两个问题:
为什么要返回Promise?
为什么时返回新的Promise,而不是原来的Promsie???

对于第一个问题,我们很容易想到返回Promsie是为了支持链式调用。在Promises/A+规范中的2.1Promise States中明确规定了,pending可以转化为fulfilled或rejected并且只能转化一次,也就是说如果pending转化到fulfilled状态,那么就不能再转化到rejected。并且fulfilled和rejected状态只能由pending转化而来,两者之间不能互相转换。一图胜千言:

Promise-status
也就是说,Promsie的状态是不可逆的。

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

  // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
  onResolved = typeof onResolved === 'function' ? onResolved : function (v) { }
  onRejected = typeof onRejected === 'function' ? onRejected : function (r) { }

  //
  switch (self.status) {
    case 'resolved':
      return new Promise(function (resolve, reject) {
        try {
          var x = onResolved(self.data)
          if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为结果
            x.then(resolve, reject)
          }
          resolve(x) // 否则,以它的返回值作为参数传递
        } catch (e) {
          reject(e) // 如果出错,以捕获到的错误做为结果
        }
      })
    case 'rejected':
      return new Promise(function (resolve, reject) {
        try {
          var x = onRejected(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
    default: // 'pending状态'
      return 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)
          }
        })
      })
  }

我们回过头来看下面这个场景:

function getUserId() {
    return new Promise(function(resolve) {
        //异步请求
        http.get(url, function(results) {
            resolve(results.id)
        })
    })
}
getUserId().then(function(id) {
    console.log(id)
    //一些处理
})

我们知道getUserId()是一个异步方法,其返回值是一个Promsie对象(暂且称之为A),当代码执行到getUserId().then()时,A的状态是未知的,可能是pending,可能是resolved,也可能是rejected。因此在上面,我们对3中情况都进行了处理:

  • pending状态。向onResolvedCallback/onRejectedCallback队列中注册回调函数。

  • resolved状态,执行onResolved函数。如果onResolve函数的返回值仍然是一个Promise,则递归调用then方法。 如果不是,用它的返回值作为结果。也许你想知道为什么这样做,其实也很简单,Promsie其实就是类似于流,对上面传来的数据进行加工处理。 现在,我们来看这样一种情形:获取用户id,然后根据用户id去获取购物车数据。
function getUserId() {
    return new Promise(function(resolve) {
        //异步请求
        http.get(url, function(results) {
            resolve(results.id)
        })
    })
}
getUserId().then(function(id) {
    return Promise(function(resolve){
        http.get(url/:id,function(results){
            resolve(results)
        }
    })
}).then((val)=>{
    console.log(val)
})

  • rejected状态。与resolved原理差不多,在此不赘诉。

bug修复(加入延时机制)

细心的同学应该发现,上述代码可能还存在一个问题:如果在then方法注册回调之前,resolve函数就执行了,怎么办?比如promise内部的函数是同步函数:

// 例3
function getUserId() {
    return new Promise(function (resolve) {
        resolve(9876);
    });
}
getUserId().then(function (id) {
    // 一些处理
});

这显然是不允许的,Promises/A+规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序。因此我们要加入一些处理,保证在resolve执行之前,then方法已经注册完所有的回调。我们可以这样改造下resolve函数:

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

同样的,对于rejected函数,我们也需要进行同样的处理

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

至此,我们已经完成了Promise的基本结构和功能.... 完整代码如下

function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise当前的状态
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
  self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面

  executor(resolve, reject) // 执行executor并传入相应的参数
  function resolve(value) {
    setTimeout(function(){
      if (self.status === 'pending') {
          self.status = 'resolved'
          self.data = value
          for(var i = 0; i < self.onResolvedCallback.length; i++) {
            self.onResolvedCallback[i](value)
          }
        }  
    },0)
  }
  function reject(reason) {
   setTimeout(function(){
        if (self.status === 'pending') {
            self.status = 'rejected'
            self.data = reason
            for(var i = 0; i < self.onRejectedCallback.length; i++) {
                self.onRejectedCallback[i](reason)
            }
        }
   },0)
  }
}
Promise.prototype.then = function (onResolved, onRejected) {
  var self = this;

  // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
  onResolved = typeof onResolved === 'function' ? onResolved : function (v) { }
  onRejected = typeof onRejected === 'function' ? onRejected : function (r) { }

  //
  switch (self.status) {
    case 'resolved':
      return new Promise(function (resolve, reject) {
        try {
          var x = onResolved(self.data)
          if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为结果
            x.then(resolve, reject)
          }
          resolve(x) // 否则,以它的返回值作为参数传递
        } catch (e) {
          reject(e) // 如果出错,以捕获到的错误做为结果
        }
      })
    case 'rejected':
      return new Promise(function (resolve, reject) {
        try {
          var x = onRejected(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
    default: // 'pending状态'
      return 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)
          }
        })
      })
  }