【Promise】实现完整版

243 阅读9分钟

目录

  1. Promise解决了什么问题
  2. Promise的使用
  3. Promise/A+规范
  4. 实现一个符合Promise/A+规范的Promise
  5. 基于观察模式实现Promise
  6. 【完整版】- 增加then方法实现链式调用

一、Promise解决了什么问题

Promise允许我们通过链式调用的方式来解决“回调地狱”的问题,特别是在异步过程中,通过Promise可以保证代码的整洁性和可读性。本文主要解读Promise/A+规范,并在此规范的基础上,自己实现一个Promise.

二、Promise的使用

1) 创建promise实例

var p=new Promise(function(resolve,reject){
    setTimeout(function(){
       resolve("success")
    },1000);
    console.log("创建一个新的promise");
})
p.then(function(x){
  console.log(x)
})

//输出:
// 创建一个新的promise
// success

上述是一个promise的实例,输出内容为,“创建一个promise”,延迟1000ms后,输出"success"。

从上述的例子可以看出,promise方便处理异步操作。此外promise还可以链式的调用:

var p=new Promise(function(resolve,reject){resolve()});
p.then(...).then(...).then(...)

此外Promise除了then方法外,还提供了Promise.resolve、Promise.all、Promise.race等等方法。

三、Promise/A+规范

Promise/A+规范扩展了早期的Promise/A proposal提案,我们来解读一下Promise/A+规范。

1) 术语

(1)"promise"是一个对象或者函数,该对象或者函数有一个then方法

(2)"thenable"是一个对象或者函数,用来定义then方法

(3)"value"是promise状态成功时的值

(4)"reason"是promise状态失败时的值

我们明确术语的目的,是为了在自己实现promise时,保持代码的规范性(也可以跳过此小节)

2) 要求

(1)一个promise必须有3个状态,pending,fulfilled(resolved),rejected当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态。当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。

promise英文译为承诺,也就是说promise的状态一旦发生改变,就永远是不可逆的

(2)一个promise必须有一个then方法,then方法接受两个参数:

promise.then(onFulfilled,onRejected)

其中onFulfilled方法表示状态从pending——>fulfilled(resolved)时所执行的方法,而onRejected表示状态从pending——>rejected所执行的方法。

(3)为了实现链式调用,then方法必须返回一个promise

promise2=promise1.then(onFulfilled,onRejected)

四、实现一个符合Promise/A+规范的Promise

解读了Promise/A+规范之后,下面我们来看如何实现一个Promise, 首先构造一个myPromise函数,关于所有变量和函数名,应该与规范中保持相同。

1) 初始版本myPromise

同时,需要在myPromise的原型上定义链式调用的then方法:

在myPromise里发生状态改变,然后在相应的then方法里面根据不同的状态可以执行不同的操作。

function myPromise(constructor){
    let self=this;
    console.log('self0:',self, 'this0:',this); // 此处的this指向 new出来的实例对象。
    self.status="pending" //定义状态改变前的初始状态
    self.value=undefined;//定义状态为resolved的时候的状态
    self.reason=undefined;//定义状态为rejected的时候的状态
    function resolve(value){
        console.log('self:',self, 'this:',this); // 此处的 this 指向window。因为此函数是属于实例对象的参数调用。
        //两个==="pending",保证了状态的改变是不可逆的
       if(self.status==="pending"){
          self.value=value;
          self.status="resolved";
       }
    }
    function reject(reason){
        //两个==="pending",保证了状态的改变是不可逆的
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
       }
    }
    //捕获构造异常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);// 异常也是要走reject(e)
    }
};

myPromise.prototype.then=function(onFullfilled,onRejected){
   let self=this;
   console.log('then 中 self:',self, 'then 中 this:',this); // 此处的this指向的是 new出来的Promise实例(  因为调用是在实例对象上直接.then((res)=>{},(rej)=>{})  ),没必要保存起来this。
   switch(self.status){
      case "resolved":
        onFullfilled(self.value); //
        break;
      case "rejected":
        onRejected(self.reason);
        break;
      default:       
   }
}

var p=new myPromise(function(resolve,reject){resolve(111)});
p.then(function(x){console.log(x)})

但是这里myPromise无法处理异步的resolve.比如:

var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});

p.then(function(x){console.log(x)})
//无输出

思考:

  • 为何myPromise无法处理异步的resolve?

五 基于观察模式实现Promise

为了处理异步resolve,我们修改myPromise的定义,用2个数组onFullfilledArray和onRejectedArray来保存异步的方法。在状态发生改变时,一次遍历执行数组中的方法。


function myPromise(constructor){
    let self=this;
    self.status="pending" //定义状态改变前的初始状态
    self.value=undefined;//定义状态为resolved的时候的状态
    self.reason=undefined;//定义状态为rejected的时候的状态
    self.onFullfilledArray=[]; // 存储 成功的回调函数
    self.onRejectedArray=[];// 存储 失败的回调函数
    function resolve(value){
       if(self.status==="pending"){ // 保证了状态的改变是不可逆的
          self.value=value;
          self.status="resolved";
          // 在pending时把状态改变为成功并把数据传过来。
          // 并遍历调用成功的回调数组
          console.log('构造函数中 resolved中的 成功回调数组:', self.onFullfilledArray);
          self.onFullfilledArray.forEach(function(f){
                f(self.value);
                //如果状态从pending变为resolved,
                //那么就遍历执行里面的异步方法
          });
        
       }
    }
    function reject(reason){
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
          self.onRejectedArray.forEach(function(f){
              f(self.reason);
             //如果状态从pending变为rejected,
             //那么就遍历执行里面的异步方法
          })
       }
    }
    //捕获构造异常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}


myPromise.prototype.then=function(onFullfilled,onRejected){
   let self=this;
   switch(self.status){
      case "pending": // 为何在 pending 时 push 成功和失败回调函数 到对应的数组中。 (保证了状态的改变是不可逆的)
        console.log('then 中的 pending时的 this 与其上value:', self);
        self.onFullfilledArray.push(function(){
             onFullfilled(self.value)
        });
        self.onRejectedArray.push(function(){
             onRejected(self.reason)
        });
        console.log('self.onFullfilledArray 成功数组:', self.onFullfilledArray);
      case "resolved": // 给传入的函数传参,并调用传入的函数
        console.log('then 中的 resolved 时的 this:', self);
        onFullfilled(self.value);
        break;
      case "rejected":
        console.log('then 中的 rejected 时的 this:', self);
        onRejected(self.reason);
        break;
      default:       
   }
}

var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(100)},1000)});

p.then(function(x){console.log(x)})

image.png

这样,通过两个数组,在状态发生改变之后再开始执行,这样可以处理异步resolve无法调用的问题。这个版本的myPromise就能处理所有的异步,那么这样做就完整了吗?

没有,我们做Promise/A+规范的最大的特点就是链式调用,也就是说then方法返回的应该是一个promise。

六 完整版-增加then方法实现链式调用

要通过then方法实现链式调用,那么也就是说then方法每次调用需要返回一个primise,同时在返回promise的构造体里面,增加错误处理部分,我们来改造then方法

function myPromise(constructor){
    let self=this;
    self.status="pending" //定义状态改变前的初始状态
    self.value=undefined;//定义状态为resolved的时候的状态
    self.reason=undefined;//定义状态为rejected的时候的状态
    self.onFullfilledArray=[]; // 存储 成功的回调函数
    self.onRejectedArray=[];// 存储 失败的回调函数
    function resolve(value){
       if(self.status==="pending"){ // 保证了状态的改变是不可逆的
          self.value=value;
          self.status="resolved";
          // 在pending时把状态改变为成功并把数据传过来。
          // 并遍历调用成功的回调数组
          console.log('构造函数中 resolved中的 成功回调数组:', self.onFullfilledArray);
          self.onFullfilledArray.forEach(function(f){
                f(self.value);
                //如果状态从pending变为resolved,
                //那么就遍历执行里面的异步方法
          });
        
       }
    }
    function reject(reason){
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
          self.onRejectedArray.forEach(function(f){
              f(self.reason);
             //如果状态从pending变为rejected,
             //那么就遍历执行里面的异步方法
          })
       }
    }
    //捕获构造异常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}

myPromise.prototype.then=function(onFullfilled,onRejected){
    let self=this;
    let promise2;
    switch(self.status){
      case "pending":
        promise2=new myPromise(function(resolve,reject){
             self.onFullfilledArray.push(function(){
                try{
                   let temple=onFullfilled(self.value);
                   resolve(temple)
                }catch(e){
                   reject(e) //error catch
                }
             });
             self.onRejectedArray.push(function(){
                 try{
                   let temple=onRejected(self.reason);
                   reject(temple)
                 }catch(e){
                   reject(e)// error catch
                 }
             });
        })
      case "resolved":
        promise2=new myPromise(function(resolve,reject){
            try{
              let temple=onFullfilled(self.value);
              //将上次一then里面的方法传递进下一个Promise的状态
              resolve(temple);
            }catch(e){
              reject(e);//error catch
            }
        })
        break;
      case "rejected":
        promise2=new myPromise(function(resolve,reject){
            try{
               let temple=onRejected(self.reason);
               //将then里面的方法传递到下一个Promise的状态里
               resolve(temple);   
            }catch(e){
               reject(e);
            }
        })
        break;
      default:       
   }
   return promise2;
}

var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(111)},1000)});
p.then(function(x){console.log(x)}).then(function(){console.log("链式调用1")}).then(function(){console.log("链式调用2")})
//输出

// 链式调用1
// 链式调用2
// 111

这样我们虽然实现了then函数的链式调用,但是还有一个问题,就是在Promise/A+规范中then函数里面的onFullfilled方法和onRejected方法的返回值可以是对象,函数,甚至是另一个promise。

待办:

参考中可见

1) then函数中的onFullfilled和onRejected方法的返回值问题

特别的为了解决onFullfilled和onRejected方法的返回值可能是一个promise的问题。

首先来看promise中对于onFullfilled函数的返回值的要求

I)如果onFullfilled函数返回的是该promise本身,那么会抛出类型错误

II)如果onFullfilled函数返回的是一个不同的promise,那么执行该promise的then函数,在then函数里将这个promise的状态转移给新的promise
III)如果返回的是一个嵌套类型的promsie,那么需要递归。

IV)如果返回的是非promsie的对象或者函数,那么会选择直接将该对象或者函数,给新的promise。

根据上述返回值的要求,我们要重新的定义resolve函数,这里Promise/A+规范里面称为:resolvePromise函数,该函数接受当前的promise、onFullfilled函数或者onRejected函数的返回值、resolve和reject作为参数。

参考

总结

  • 在Promise构造函数中,如果状态从pending变为resolved,那么就遍历执行里面的异步方法

观察者模式理解

1) 目标对象

  • 目标对象(被观察者)就是Promise构造函数的 实例, 实例上面存储了成功和失败的回调数组,在这里存储的实际上就是观察者列表
self.onFullfilledArray=[{},{}];

那么,这些观察者是从哪里来的,push

then中的 push就是在push观察者实例观察者列表中

// 在某个时机执行

状态从pending变为rejected此时机,执行所有的观察者列表中的观察者对象实例上传入的回调。

2) 观察者

var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});

p.then(function(x){console.log(x)})

p为目标对象给目标对象传入的cb为观察者

目标对象状态改变为 成功/失败时, 就执行对应 成功/失败 观察者列表中观察者回调

2) 目标对象 观察者 构成分析

  • 目标对象 包含: 观察者数组遍历执行这些观察者实例传入的回调函数(也就是调用这些观察者实例对象上的update)
  • 观察者实例包含: 回调函数,收到目标对象通知时执行

注意: 此Pomise观察者模式的实现中 观察者没有提供给 目标对象调用update方法被目标对象通知时执行的update方法,用于调用 观察者实例传入的 回调函数