最简单!实现Promise的两种思路分析

引言

Promise是一种异步编程的解决方案,经过链式调用的方式解决回调地狱。做为前端面试中的考点,也是前端的基本功,掌握其原理是很是重要的。本次分享就从Promise的使用方式上出发,一步一步剖析其原理,最后帮助你们封装出本身的Promise前端

注:若是你还不了解Promise,建议点击这里学习Promise的基本使用语法。es6

本文知识点:面试

  • 非规范的(简单粗暴)Promise的原理
  • 规范的(Promises/A+Promise的原理

正文


知其然才能知其因此然,咱们先来看一下最常使用的例子,分析有什么特征。编程

使用例子

熟悉 Promise使用的人都知道,当调用 getNews()返回一个新的 Promise时,里面的异步调用操做将会 马上执行,而后在异步回调里调用 resolve方法和 reject方法改变 Promise的状态,执行对应的 then或者 catch函数。

以上代码有如下几个特征:segmentfault

  1. Promise是一个构造函数,其接受一个函数做为参数。
  2. 做为参数的函数里,有两个方法resolvereject
  3. Promise带有方法thencatch

resolvereject是怎么来的?Promise实例化的时候都作了什么?数组

别急,所谓生死看淡,不服就干。在回答这两个问题以前,咱们能够先直接尝试构建本身的Promisepromise

开始构建

  1. 构造函数

fn就是咱们使用时传入的回调函数。

  1. resolvereject

那么fn是何时调用的呢?其实,在Promise实例初始化的时候内部就自动调用了,而且传入了内部的resolvereject方法给开发者调用,就像下面这样:bash

至此,第一个问题获得了回答,即: resolverejectPromise 内部提供给开发者的。

  1. 添加thencatch方法

这两个方法是Promise实例的方法,所以应该写在this或者prototype上。异步

到这里,一个 Promise基本的骨架就出来了,下面咱们仔细唠唠这4个函数的具体做用。

做用分析

  1. resolvereject

想象一下咱们平常使用Promise的场景,在异步请求以后,是须要咱们手动调用resolvereject方法去改变状态的。编辑器

resolve调用意味着异步请求已经有告终果,能够执行 then里面的回调了( reject同理,异步请求失败时候执行 catch函数。)

  1. thencatch

调用上述的函数时以下:

咱们知道,在resolve被调用前,thencatch函数里面的回调是不会执行的。那么咱们这样写的时候,它作了什么呢?

实际上,Promise悄悄把咱们写的回调函数保存了起来,等到咱们手动调用resolve或者reject时才依次去执行。也就是说,Promise里的thencatch的做用就是:注册回调函数,先把一系列的回调函数存起来,等到开发者调用的时候才拿出来执行。

因此,thencatch 函数应该长这样:

resolvereject就是分别去调用他们而已。

到目前为止能够回答第二个问题了: Promise初始化时,内部调用了咱们传入的函数,并将 resolvereject方法做为参数提供。在以后调用的 then或者 catch方法里,把回调函数保存在内部的一个队列中,等待状态改变时候调用。

链式调用

接下来咱们实现普通的链式调用,实现链式调用很是简单。因为then是挂载到this上的方法,若是咱们在then中直接返回this就能够实现链式调用了。就像这样:

then函数返回的仍是实例对象自己,因此就能够一直调用 then方法。同时 okCallback应该变成一个数组,才能保存屡次调用 then方法的回调。而当 okCallback是一个数组时,调用resolve方法就须要遍历 okCallback,依次调用,就像下面这样:

resolve中,每次调用函数的返回值将会成为下一个函数的参数。以此就能够进行 then回调的参数传递了。

状态引入和延时机制

Promise规范里,最初的状态是pending,当调用resolve以后转变为fulfilled,而调用reject以后转变为rejected。状态只能转变一次。

另外,为了保证resolve调用时,then已经所有注册完毕,还应该引入setTimeout延迟resolve的执行:

链式调用Promise(重点)

实际开发中,常常会遇到有先后顺序要求的异步请求,咱们每每会在then回调里返回一个Promise实例,这意味着咱们须要等待这个新的Promise实例resolve以后才能继续进行下面的then调用。

目前咱们的 MyPromise显然不能支持这种场景,那么怎么才能实现这个执行权的交替呢?

有一个很简单的方法,还记得then函数的做用吗?

对,注册回调

既然它返回了一个新的Promise致使咱们不能正常执行后续的then回调,那咱们直接把后续的then回调所有转移到这个新的Promise上,让它代替执行不就行了吗?

当判断返回值为 Promise实例时,直接调用新实例的 then方法注册剩余的回调,而后直接 return,等到新实例 resolve时,就会继续代替执行剩下的 then回调了。

完了吗?完了,到这里已经可以实现Promise的链式调用了,也就说今天的8分钟你已经能够写出本身的Promise了,恭喜!


不过,这种作法并不是Promise标准,想知道在Promise标准里是怎么解决执行权的转交问题吗?也不复杂,可是须要你有很是好的耐心去仔细理解里面的逻辑,准备好了就接着往下看吧~

Promises/A+标准下的链式调用

(为了简化模型,这里咱们只分析resolve部分的逻辑,这将涉及到3个函数:thenhandleresolve)

这三个函数的具体做用:

then:此时then函数再也不返回this,而是直接返回一个全新的Promise,这个Promise就是链接两个then之间的桥梁。

handle:当状态为pending时,注册回调。不然直接调用。

resolve:尝试遍历执行注册的回调。若是参数是一个promise实例,则将执行权移交给新的promise,自身暂停执行。

看到这里是否是以为很复杂?其实也不复杂,一句话就能够归纳:

在promises/A+规范里,后一个promise保存了前一个promise 的resolve引用。

前一个 resolve会带动后一个 resolve,当 resolve的参数是 Promise实例时,暂停自身 resolve的调用,把自身做为引用传递给新的 Promise实例,新的 Promise实例的 resolve会引发自身 resolve

若是还不理解的话,咱们能够实际看一个例子。(请一边看例子,一边对照着代码思考哦,代码在最后附录,建议粘贴到本地编辑器对照着思考。)

比基尼海滩的海绵宝宝想要外卖一个蟹黄堡,他必须首先上网查到蟹堡王的外卖电话,而后才能点外卖。用JavaScript描述就是下面这样:

在最开始的阶段,一共会生成3个 promise,分别是 getPhoneNumber(),第一个 then()和第二个 then()( getHamburger还未调用,所以没有计算在内)

让咱们来看看对应的代码执行吧。如今是注册回调阶段,第一个then返回的promise将会把自身的resolve引用传递给getPhoneNumber()handle函数,而handle函数会同时把resolve应用和对应的then回调一同保存起来:

第二个then同理,因此注册阶段结束后,各个promise内部的状态以下图所示:

在调用阶段,会随着getPhoneNumber()resolve引起后续的resolve,整个过程能够用下图表示:

  1. 首先,getPhoneNumber()触发resolve,返回值是number,所以能够遍历调用callbacks里保存的回调。
  2. 进入handle函数,改变getPhoneNumber()state,以后函数①被调用,该函数返回getHamburger()。调用第一个thenresolve引用,并将getHamburger()做为参数传递。
  3. 视线转移到第一个then()。在判断参数getHamburder()是一个Promise实例以后,将自身的resolve做为回调,调用其then方法(能够看到上图中,getHamburgercallbacks里保存的实际上是前一个thenresolve引用,由于此时前面的Promise被中断了,所以当开发者调用getHamburger()resolve方法时,才能继续未完成的resolve执行。)
  4. 等到getHamburger()resolve调用时,实际上就会调用上一个thenresolve,返回值做为参数传递给右边的then,使其resolve
  5. 视线再一次到第一个then()上。进入自身的handle方法,改变state,以后函数②被调用,触发第二个then()(也就是上图最右边)的resolve
  6. 最后,调用最后一个then()resolve(也就是上图中的小then(),这个then()是代码自动调用生成的。),整个异步过程结束。

总结

Promise使用一个resolve函数让咱们不用面临回调地狱(由于回调已经经过链式的方式注册保存起来了),实际上作的就是一层封装。其中最难理解的部分就是Promise的链式调用。本次跟你们分享的第一种方式很是简单粗暴,即把未执行完的回调转交给下一个Promise便可。第二种方式本着不抛弃不放弃的原则,多个then函数经过resolve引用连成一气,前面的resolve将可能会引发后面一系列的resolve,很有多米诺骨牌的感受。

附录

Promises/A+ 规范代码示例(来源:参考[1])

function MyPromise(fn) {
  var state = 'pending',
      value = null,
      callbacks = [];

  this.then = function (onFulfilled) {
      return new MyPromise(function (resolve) {
          handle({
              onFulfilled: onFulfilled || null,
              resolve: resolve
          })
      })
  }

  let handle = (callback) => {
      if (state === 'pending') {
          callbacks.push(callback)
          return
      }
      //若是then中没有传递任何东西
      if(!callback.onFulfilled) {
          callback.resolve(value)
          return
      }

      var ret = callback.onFulfilled(value)
      callback.resolve(ret)
  }

  
  let resolve = (newValue) => {
      if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
          var then = newValue.then
          if (typeof then === 'function') {
              then.call(newValue, resolve)
              return
          }
      }
      state = 'fulfilled'
      value = newValue
      setTimeout(function () {
          callbacks.forEach(function (callback) {
              handle(callback)
          })
      }, 0)
  }

  fn(resolve)
}
复制代码

参考

  1. 30分钟,让你完全明白Promise原理 mengera88 2017-05-19
  2. 深刻浅出Nodejs 朴灵 P90
相关文章
相关标签/搜索