Promise实现的基本原理(二)

栏目: JavaScript · 发布时间: 5年前

内容简介:距离我上次写的那篇参考链接:github上的完整实现:

距离我上次写的那篇 Promise实现的基本原理(一) 有十几天了(太懒了),这篇我想写Promise是如何实现串联调用then,解决回调地狱的。虽然说《深入理解ES6》这本书把Promise用法讲得很详细,但是自己理解起来还是有很多疑问(我好想上一篇也是这么说的)。

参考链接: JavaScript Promises ... In Wicked Detail

github上的完整实现: Bare bones Promises/A+ implementation

一、Promise可能会出现的异步情况

情况一:对resolve的调用是异步的

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('resolve')
    console.log('Promise setTimeout')
  }, 1000)
}).then(result => {
  // 这个箭头函数我们叫 fn1
  console.log(result)
}, error => {
  // 这个箭头函数我们叫 fn2
  console.log(error)
})
复制代码

情况二:对then的调用是异步的

let p1 = new Promise((resolve, reject) => {
  resolve('resolve')
  console.log('Promise setTimeout')
})

setTimeout(() => {
  p1.then(result => {
    // 这个箭头函数我们叫 fn1
    console.log(result)
  }, error => {
    // 这个箭头函数我们叫 fn2
    console.log(error)
  })
}, 1000)
复制代码

情况三:调用resolve和调用then都是异步的

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('resolve')
    console.log('Promise setTimeout')
  }, 1000)
})

setTimeout(() => {
  p2.then(result => {
    // 这个箭头函数我们叫 fn1
    console.log(result)
  }, error => {
    // 这个箭头函数我们叫 fn2
    console.log(error)
  })
}, 1000)
复制代码

第三种情况是前两种情况的结合,如果第一个 setTimeout 时间比第二个 setTimeout 时间长,那就是第一种情况;如果第二个 setTimeout 比第一个 setTimeout 的时间长,那就是第二种情况。了解 event-loop 的同学看着几种情况应该比较清晰,不了解的同学就看我下面的分析。

二、Promise三种状态是如何工作的

我从《深入理解ES6》 Promise 那章截取了一下内容:

每个 Promise 都会经历一个短暂的生命周期:先是处于进行中( padding )的状态,此时操作尚未完成,所以他也是未处理( unsettled )的;一旦异步操作执行结束, Promise 则变为已处理( settled )的状态,操作结束后 Promise 可能会进入到以下两个状态的其中一个:

Promise
Promise

Promise 的内部属性 [[PromiseState]] 被用来表示 Promise 的3中状态: "pedding""fulfilled""rejected"

实现代码:

function Promise(fn) {
  var state = 'pending';            // 初始状态为padding
  var value;                        // Promise处于被处理状态下保存下来的值
  var deferred = null;              // 延期执行函数(现在还看不出来它是函数)

  /**
   * 异步操作成功完成回调函数
   * 在new Promise的时候被调用的(不清楚为什么的可以看上一篇博客)
   */
  function resolve(newValue) {   
    // 如果在then的回调函数里面返回了一个Promise
    if(newValue && typeof newValue.then === 'function') {
      // 将这个Promise的then的两个回调参数的引用指向Promise的resolve和reject
      newValue.then(resolve, reject);   
      return;
    }
    
    // 如果传进来的不是Promise
    state = 'resolved';             // 将Promise的状态改成resolved(fulfilled)
    value = newValue;               // 保存return过来的值

    if(deferred) {                  // 判断是否存在传进来的回调函数
      handle(deferred);             // 如果有,将我们传进来的回调函数交给handle
    }                               //(不清楚为什么的可以看上一篇博客)
  }

  /**
   * 异步操作失败/被拒绝回调函数
   * 在 new Promise 的时候被调用的
   */
  function reject(reason) {         
    state = 'rejected';             // 将Promise的状态改成rejected
    value = reason;                 // 保存我们传进来的被拒绝原因

    if(deferred) {                  // 和 resolve同理
      handle(deferred);             
    }
  }

  function handle(handler) {
    // handle被调用了,但是此时promise的状态还是处于出事的未处理状态
    if(state === 'pending') {
      deferred = handler;           // 就将handler的引用先保存下来
      return;                       // 不执行下面的代码
    }
    
    var handlerCallback;            // Promise处理完成的状态只能是一种(fulfilled/rejected),所以用同一个变量保存回调的引用

    if(state === 'resolved') {      // 如果已经成功处理(fulfilled)
      handlerCallback = handler.onResolved;     // 引用是处理成功回调的引用
    } else {
      handlerCallback = handler.onRejected;     // 如果不是,引用是处理失败回调的引用
    }

    if(!handlerCallback) {          // 如果在调用then的时候没有给then传回调函数,即上一个if语句保存下来的引用是undefined的
      if(state === 'resolved') {    // 如果状态已经是resolved(fulfilled)的
        handler.resolve(value);     // 就调用hander的resolved(下一个then传入的成功回调函数)
      } else {
        handler.reject(value);      // 否则,调用handler的reject(下一个then传入的第二个参数,即失败回调函数)
      }
      return;                       // 不执行下面的语句
    }
    
    // 如果本次调用的then有传入回调函数
    var ret = handlerCallback(value);   
    handler.resolve(ret);           // 就给
  }

  this.then = function(onResolved, onRejected) {
    return new Promise(function fn3 (resolve, reject) {  // then 返回一Promise
      // 返回的Promise把then传进来的两个回调和本省的两个回调都丢给handle处理
      handle({                                      
        onResolved: onResolved,
        onRejected: onRejected,
        resolve: resolve,
        reject: reject
      });
    });
  };

  fn(resolve, reject);
}
复制代码

回调函数引用分析:

fn函数的引用在上一篇已经分析过了,这一片重点分析在then中返回Promise给handle函数传过去的对象属性引用

  • onResolved : 在我们用 Promise 对象调用 then 的时候传过来的第一个参数 fn1 (我在注释里给了个名字好区分,就不像第一篇那样一个一个拆出来了,有点懒)。
  • onReject : 在我们用 Promise 对象调用 then 的时候传过来的第二个参数fn2。
  • resolve : then 返回的 Promise 对象传过去的 fn3 , 当 fn3 被调用时 Promise 传给它的第一个参数就是 resolve 的引用,所以 resolve (这是key) 的引用是 Promise 的内部函数 resolve (这是function)。当然,每一次 new Promise 都会是一个新的对象,所以内部的 resolve 的引用也是不一样的。
  • rejectrejectresolve 同理,指向 Promise 内部函数 reject

handle 函数分析

1、 handle 参数分析

handle 函数的调用还是在用户调用 then 的时候调用,只不过这次 then 函数返回了一个 Promise ,给 handle 函数传入的值既保留了本次调用 then 传入的两个回调函数的引用,也保留了新的 Promise 的回调函数引用。

2、结合三种状态分析

(1) "pending" 状态:

    当调用 handle 函数时 Promise 处于 padding 对应的情况是 情况一 ,此时用户创建了 Promise 对象后立即调用了 then 函数,但 resolve 还没有被处理,所以 Promise 状态没有改变, then 的回调函数需要等待 resolve 被处理了才可以执行。所以当 padding 状态的时候,将 handler 这个参数交给 deferred 暂存。

(2) "resolved" / rejected 状态:

    一秒后, resolve 回调函数被调用了(关于 resolve 的指向可以看第一篇), Promisestate 被置为 "resolved" 了,然后只要 deferred 有指向,就再一次交给 handle 函数处理一次。

    此时 Promise 的状态是 "resolved" ,将 handlerCallback 的引用指向fn1,又将 handleCallback 返回的值保存在了 ret 中,同时又传给 then 返回的 Promiseresolve ,让返回的 Promise 处于已处理状态(只有当 resolve 被执行了, then 的回调函数才会执行,才能够实现 then 的无限串联调用),也就是一下这种情况:

let p = new Promise((resolve, reject) => {
  resolve(42)
})

p.then().then().then(result => {
    console.log(result)   // 控制台输出42
})
复制代码

三、如何实现 then 的串联使用

上面那一点其实就可以看出, Promise 是通过在 then 函数里面返回了一个 Promise ,实现了 Promisethen 的串联使用。但上面的那情况并没有真正体现 Promise 的作用,请看以下代码:

new Promise((resolve, result) => {  // Promise1
  resolve(1)
}).then(result => { // Promise1的then
  console.log(result)   // 1
  return new Promise((resolve, reject) => { // Promise3
    resolve(2)
  })
}) // 此处得到Promise2
.then(result => { // Promise2的then
  console.log(result)   // 2
  return 3
}) // 此处得到Promise4
.then(result => { // Promise4的then
  console.log(result)   // 3
}) // 此处得到Promise5
复制代码

在实现代码里面可以看到 then 时返回了一个新的 Promise 的,然后再看上面的代码,在调用 then 的第一个回调函数回调的时候,我们给他返回了一个参数,一种是 Promise 对象,一种是返回一个数值,到这里我的疑问是 then 内部返回的 Promise 是如何拿到这些值的,以下分析:point_up_2:上面的代码

Promise个数分析

上面那段代码里里外外有5个Promise,先起个小名,分别是:

new
then
then
then
then

1、Promise1:

handler 参数四个属性指向:

  • onResolved : Promise1的 then 的第一个参数(回调函数)
  • onRejected : Promise1的 then 的第二个参数(回调函数)
  • resolve : Promise2的内部函数 resolve
  • reject : Promise2的内部函数 reject

Promise1当前的状态是 "resolved"

then 被调用,所以 handle 被调用blablabla~

handleCallback 指向 handler.onResolved

各种判断赋值后到了 handle 的最后两行,此时 ret 接收到 handleCallback 返回的值,然后这个值是Promise3。接着调用了 handle.resolve ,并且给它传了 ret ,而 handle.resolve 指向的是Promise2的 resolve ,所以接下来就跳到Promise2了。

2、Promise2:

handler 参数四个属性指向:

  • onResolved : Promise2的内部函数 resolve
  • onRejected : Promise2的内部函数 reject
  • resolve : Promise4的内部函数 resolve
  • reject : Promise4的内部函数 reject

Promise2的 resolve 在Promise1的 handle 函数的最后一条语句被调用,并且传入的 ret 是一个 Promise (Promise3)

所以Promise2的 resolve 函数中的第一个语句成立,但Promise2的状态不改变, newValue 指向的是Promise3,需要等待Promise3被处理。

接着Promise3的 then 被调用,所以接下要调到Promise3了。

3、Promise3

handler 参数四个属性指向:

  • onResolved : Promise2的内部函数 resolve
  • onRejected : Promise2的内部函数 reject
  • resolve : Promise3的 then 返回的 Promise 的内部函数 resolve (在当前例子不是很重要,忽略)
  • reject : Promise3的 then 返回的 Promise 的内部函数 reject (在当前例子不是很重要,忽略)

在Promise2调用Promise3的 then 的时候,Promise3的状态未知,但不管怎样,只要 Promise 的状态改变了,就会调用一次handle函数。

某个时刻Promise3的状态变为了 "resolved" / "rejected" 或者在Promise2调用它的时候就已经是 "resolved" / "rejected" 状态

还是到 handl e函数的最后两行,和Promise1的一样, handler.onResolved 会被执行,并且把Promise3的 resolve 回调时得到的值 value 传给 Promise2 的内部函数 resolve ,Promise2变为了 "resolved" 状态,并且的到了Promise3被处理后的 value

Promise3此时的任务已经完成了,被处理了,并且把得到的 value 传给了Promise2,拜拜:wave:。

4、回到Promise2

handler 参数四个属性的指向还是和之前一样,在Promise3被处理之后,Promise2的状态也会跟着Promise3的状态变化,并且拿到了Promise3的 value ,所以先在Promise2的 value 是2

Promise2状态变为已处理之后,不管什么时候调用 thenthen 的两个回调函数其中一个会被回调,所以 Promise 的这种处理,会让我这种菜:chicken:觉得 then 的回调函数里面 returnPromise 是变成了 thenreturn ,但其实Promise做的事情只是保存值而已。

5、Promise4

Promise4的 handle r参数属性指向其实和Promise2是差不多的,和Promise2不同的是: ret 的到不是一个 Promise ,而是一个具体的值。也就是说 newValue = 3 ,newValue不是 Promise 就好办了,把状态改成 "resolved" ,保存一下 return 过来的值,其他和最开始的 Promise 处理方式一样,完事。

总结

Promise 能够串联调用 then ,还是保存值和各种引用的转换, Promise 大法好!


以上所述就是小编给大家介绍的《Promise实现的基本原理(二)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构

数据结构

霍罗威茨 / 机械工业出版社 / 2006-7-1 / 48.00元

《数据结构》(C语言版)针对采用ANSI C实现数据结构进行了全面的描述和深入的讨论。书中详细讨论了栈、队列、链表以及查找结构、高级树结构等功能,对裴波那契堆、伸展树、红黑树、2-3树、2-3-4树、二项堆、最小-最大堆、双端堆等新的数据结构进行了有效分析。《数据结构》(C语言版)对一些特殊形式的堆结构,诸如应用在双端优先队列中的最小-最大堆和双端堆的数据结构以及左高树、裴波那契堆、二项堆等数据结......一起来看看 《数据结构》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器