Promise简单实现(正常思路版)

转自: http://www.jianshu.com/p/473cd754311fjavascript

 

Promise

看了些promise的介绍,仍是感受不够深刻,这个在解决异步问题上是一个很好的解决方案,因此详细看一下,顺便按照本身的思路实现一个简单的Promise。html

Promise/A+规范:

  • 首先从新阅读了下A+的规范:
    • promise表明了一个异步操做的最终结果,主要是经过then方法来注册成功以及失败的状况,
    • Promise/A+历史上说是实现了Promise/A的行为而且考虑了一些不足之处,他并不关心如何建立,完成,拒绝Promise,只考虑提供一个可协做的then方法。

术语:

  • promise是一个拥有符合上面的特征的then方法的对象或者方法。
  • thenable是定义了then方法的对象或者方法
  • value是任何合法的js的值(包括undefined,thenable或者promise)
  • exception是一个被throw申明抛出的值
  • reason是一个指明了为何promise被拒绝

状态要求:

  • promise必须是在pending,fulfilled或者rejected之间的一种状态。
  • promise一旦从pending变成了fulfilled或则rejected,就不能再改变了。
  • promise变成fulfilled以后,必须有一个value,而且不能被改变
  • promise变成rejected以后,必须有一个reason,而且不能被改变

then方法的要求:

  • promise必须有个then方法来接触当前的或者最后的value或者reason
  • then方法接受两个参数,onFulfilled和onRejected,这两个都是可选的,若是传入的不是function的话,就会被忽略
  • 若是onFulfilled是一个函数,他必须在promise完成后被执行(不能提早),而且value是第一个参数,而且不能被执行超过一次
  • 若是onRejected是一个函数,他必须在promise拒绝后被执行(不能提早),而且reason是第一个参数,而且不能被执行超过一次
  • onFulfilled或者onRejected只能在执行上下文堆只包含了平台代码的时候执行(就是要求onfulfilled和 onrejected必须异步执行,必须在then方法被调用的那一轮事件循环以后的新执行栈执行,这里可使用macro-task或者micro- task,这两个的区别参见文章)
  • onFulfilled或者onRejected必须做为function被执行(就是说没有一个特殊的this,在严格模式中,this就是undefined,在粗糙的模式,就是global)
  • then方法可能在同一个promise被调用屡次,当promise被完成,全部的onFulfilled必须被顺序执行,onRejected也同样
  • then方法必须也返回一个promise(这个promise能够是原来的promise,实现必须申明什么状况下二者能够相等)promise2 = promise1.then(onFulfilled, onRejected);java

    • 若是onFulfilledonRejected都返回一个value x,执行2.3Promise的解决步骤[Resolve]
    • 若是onFulfilledonRejected都抛出exception e,promise2必须被rejected一样的e
    • 若是onFulfilled不是个function,且promise1 is fulfilled,promise2也会fulfilled,和promise1的值同样
    • 若是onRejected不是个function,且promise1 is rejected,promise2也会rejected,理由和promise1同样

    这里不论promise1被完成仍是被拒绝,promise2 都会被 resolve的,只有出现了一些异常才会被rejectedgit

Promise的解决步骤==[Resolve]

  • 这个是将promise和一个值x做为输入的一个抽象操做。若是这个x是支持then的,他会尝试让promise接受x的状态;不然,他会用x的值来fullfill这个promise。运行这样一个东西,遵循如下的步骤
    • 若是promise和x指向同一个对象,则reject这个promise使用TypeError。
    • 若是x是一个promise,接受他的状态
    • 若是x在pending,promise必须等待x的状态改变
    • 若是x被fullfill,那么fullfill这个promise使用同一个value
    • 若是x被reject,那么reject这个promise使用同一个理由
    • 若是x是一个对象或者是个方法
    • 若是x.then返回了错误,则reject这个promise使用错误。
    • 若是then是一个方法,使用x为this,resolvePromise为一参,rejectPromise为二参,
      • 若是resolvePromise被一个值y调用,那么运行[Resolve]
      • 若是rejectPromise被reason r,使用r来reject这个promise
      • 若是resolvePromise和rejectPromise都被调用了,那么第一个被调用的有优先权,其余的beihulue
      • 若是调用then方法获得了exception,若是上面的方法被调用了,则忽略,不然reject这个promise
    • 若是then方法不是function,那么fullfill这个promise使用x
    • 若是x不是一个对象或者方法,那么fullfill这个promise使用x

若是promise产生了环形的嵌套,好比[Resolve]最终唤起了[Resolve],那么实现建议且并不强求来发现这种循环,而且reject这个promise使用一个TypeError。github

接下来正式写一个promise

思路都是最正常的思路,想要写一个Promise,确定得使用一个异步的函数,就拿setTimeout来作。数组

var p = new Promise(function(resolve){
    setTimeout(resolve, 100);
});
p.then(function(){console.log('success')},function(){console.log('fail')});

初步构建

上面是个最简单的使用场景咱们须要慢慢来构建promise

function Promise(fn){
  //须要一个成功时的回调
  var doneCallback;
  //一个实例的方法,用来注册异步事件
  this.then = function(done){
    doneCallback = done;
  }
  function resolve(){
    doneCallback();
  }
  fn(resolve);
}

加入链式支持

下面加入链式,成功回调的方法就得变成数组才能存储异步

function Promise(fn){
  //须要成功以及成功时的回调
  var doneList = [];
  //一个实例的方法,用来注册异步事件
  this.then = function(done ,fail){
    doneList.push(done);
    return this;
  }
  function resolve(){
    doneList.forEach(function(fulfill){
      fulfill();
    });
  }
  fn(resolve);
}

这里promise里面若是是同步的函数的话,doneList里面仍是空的,因此能够加个setTimeout来将这个放到js的最后执行。这里主要是参照了promiseA+的规范,就像这样函数

function resolve(){
  setTimeout(function(){
    doneList.forEach(function(fulfill){
      fulfill();
    });
  },0);
}

加入状态机制

这时若是promise已经执行完了,咱们再给promise注册then方法就怎么都不会执行了,这个不符合预期,因此才会加入状态这种东西。更新过的代码以下学习

function Promise(fn){
  //须要成功以及成功时的回调
  var state = 'pending';
  var doneList = [];
  //一个实例的方法,用来注册异步事件
  this.then = function(done){
    switch(state){
      case "pending":
        doneList.push(done);
        return this;
        break;
      case 'fulfilled':
        done();
        return this;
        break;
    }
  }
  function resolve(){
    state = "fulfilled";
    setTimeout(function(){
      doneList.forEach(function(fulfill){
        fulfill();
      });
    },0);
  }
  fn(resolve);
}

加上异步结果的传递

如今的写法根本没有考虑异步返回的结果的传递,咱们来加上结果的传递

function resolve(newValue){
  state = "fulfilled";
  var value = newValue;
  setTimeout(function(){
    doneList.forEach(function(fulfill){
      value = fulfill(value);
    });
  },0);
}

支持串行

这样子咱们就能够将then每次的结果交给后面的then了。可是咱们的promise如今还不支持promise的串行写法。好比咱们想要

var p = new Promise(function(resolve){
    setTimeout(function(){
      resolve(12);
    }, 100);
});
var p2 = new Promise(function(resolve){
    setTimeout(function(){
      resolve(42);
    }, 100);
});
p.then(
      function(name){
        console.log(name);return 33;
      }
  )
  .then(function(id){console.log(id)})
  .then(p2)
  .then(function(home){console.log(home)});

因此咱们必须改下then方法。

当then方法传入通常的函数的时候,咱们目前的作法是将它推动了一个数组,而后return this来进行链式的调用,而且指望在resolve方法调用时执行这个数组。

最开始我是研究的美团工程师的一篇博客,到这里的时候发现他的解决方案比较跳跃,因而我就按照普通的正常思路先尝试了下:

若是传入一个promise的话,咱们先尝试继续推入数组中,在resolve的地方进行区分,发现是可行的,我先贴下示例代码,而后会有详细的注释。

function Promise(fn){
  //须要成功以及成功时的回调
  var state = 'pending';
  var doneList = [];
  this.then = function(done){
    switch(state){
      case "pending":
        doneList.push(done);
        return this;
        break;
      case 'fulfilled':
        done();
        return this;
        break;
    }
  }
  function resolve(newValue){
    state = "fulfilled";
    setTimeout(function(){
      var value = newValue;
      //执行resolve时,咱们会尝试将doneList数组中的值都执行一遍
      //当遇到正常的回调函数的时候,就执行回调函数
      //当遇到一个新的promise的时候,就将原doneList数组里的回调函数推入新的promise的doneList,以达到循环的目的
      for (var i = 0;i<doneList.length;i++){
        var temp = doneList[i](value)
        if(temp instanceof Promise){
            var newP =  temp;
            for(i++;i<doneList.length;i++){
                newP.then(doneList[i]);
            }
        }else{
            value = temp;
        }
      }
    },0);
  }
  fn(resolve);
}
var p = function (){
    return new Promise(function(resolve){
        setTimeout(function(){
          resolve('p 的结果');
        }, 100);
    });
}
var p2 = function (input){
    return new Promise(function(resolve){
        setTimeout(function(){
            console.log('p2拿到前面传入的值:' + input)
            resolve('p2的结果');
        }, 100);
    });
}
p()
.then(function(res){console.log('p的结果:' + res); return 'p then方法第一次返回'})
.then(function(res){console.log('p第一次then方法的返回:'+res); return 'p then方法第二次返回'})
.then(p2)
.then(function(res){console.log('p2的结果:' + res)});

加入reject

我按照正常思路这么写的时候发现出了点问题,由于按照最上面的规范。即便一个promise被rejected,他注册的then方法以后再注册的 then方法会可能继续执行resolve的。即咱们在then方法中为了链式返回的this的status是可能会被改变的,假设咱们在实现中来改变状 态而不暴露出来(这其实一点也不推荐)。

我直接贴实现的代码,还有注释做为讲解

function Promise(fn){
  var state = 'pending';
  var doneList = [];
  var failList= [];
  this.then = function(done ,fail){
    switch(state){
      case "pending":
        doneList.push(done);
        //每次若是没有推入fail方法,我也会推入一个null来占位
        failList.push(fail || null);
        return this;
        break;
      case 'fulfilled':
        done();
        return this;
        break;
      case 'rejected':
        fail();
        return this;
        break;
    }
  }
  function resolve(newValue){
    state = "fulfilled";
    setTimeout(function(){
      var value = newValue;
      for (var i = 0;i<doneList.length;i++){
        var temp = doneList[i](value);
        if(temp instanceof Promise){
            var newP =  temp;
            for(i++;i<doneList.length;i++){
                newP.then(doneList[i],failList[i]);
            }
        }else{
            value = temp;
        }
      }
    },0);
  }
  function reject(newValue){
    state = "rejected";
    setTimeout(function(){
      var value = newValue;
      var tempRe = failList[0](value);
      //若是reject里面传入了一个promise,那么执行完这次的fail以后,将剩余的done和fail传入新的promise中
      if(tempRe instanceof Promise){
        var newP = tempRe;
        for(i=1;i<doneList.length;i++){
            newP.then(doneList[i],failList[i]);
        }
      }else{
        //若是不是promise,执行完当前的fail以后,继续执行doneList
        value =  tempRe;
        doneList.shift();
        failList.shift();
        resolve(value);
      }
    },0);
  }
  fn(resolve,reject);
}
var p = function (){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
          reject('p 的结果');
        }, 100);
    });
}
var p2 = function (input){
    return new Promise(function(resolve){
        setTimeout(function(){
            console.log('p2拿到前面传入的值:' + input)
            resolve('p2的结果');
        }, 100);
    });
}
p()
.then(function(res){console.log('p的结果:' + res); return 'p then方法第一次返回'},function(value){console.log(value);return 'p then方法第一次错误的返回'})
.then(function(res){console.log('p第一次then方法的返回:'+res); return 'p then方法第二次返回'})
.then(p2)
.then(function(res){console.log('p2的结果:' + res)});

 

这篇文章是本身根据比较正常的思路来写的一个简单的promise。

接下来还会有篇文章来自习研究下美团那篇博客以及一些主流的promise的写法,敬请期待。

参考:

先写到这里,顺便给个github的传送门,喜欢的朋友star一下啊,本身平时遇到的问题以及一下学习的经历及写代码的思考都会在github上进行记录~

相关文章
相关标签/搜索