Promise实现的基本原理(二)

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

内容简介:距离我上次写的那篇参考链接: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 的引用也是不一样的。
  • reject : reject 和 resolve 同理,指向 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 的指向可以看第一篇), Promise 的 state 被置为 "resolved" 了,然后只要 deferred 有指向,就再一次交给 handle 函数处理一次。

    此时 Promise 的状态是 "resolved" ,将 handlerCallback 的引用指向fn1,又将 handleCallback 返回的值保存在了 ret 中,同时又传给 then 返回的 Promise 的 resolve ,让返回的 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 ,实现了 Promise 的 then 的串联使用。但上面的那情况并没有真正体现 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状态变为已处理之后,不管什么时候调用 then , then 的两个回调函数其中一个会被回调,所以 Promise 的这种处理,会让我这种菜:chicken:觉得 then 的回调函数里面 return 的 Promise 是变成了 then 的 return ,但其实Promise做的事情只是保存值而已。

5、Promise4

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

总结

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


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

查看所有标签

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

Structure and Interpretation of Computer Programs - 2nd Edition

Structure and Interpretation of Computer Programs - 2nd Edition

Harold Abelson、Gerald Jay Sussman / The MIT Press / 1996-7-25 / USD 145.56

Structure and Interpretation of Computer Programs has had a dramatic impact on computer science curricula over the past decade. This long-awaited revision contains changes throughout the text. Ther......一起来看看 《Structure and Interpretation of Computer Programs - 2nd Edition 》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具