美文网首页
深刻理解Promise系列(二):手把手教你实现Promise(

深刻理解Promise系列(二):手把手教你实现Promise(

作者: 任无名F | 来源:发表于2017-06-27 01:41 被阅读0次

:经过作者的进一步学习,觉得有更好的串行Promise的实现,有兴趣的读者可以移步这里


串行的Promise,质的飞跃

接下来要介绍的,是Promise最为有趣与神秘的功能——串行Promise。
它的效果是当前promise达到fulfilled状态之后,会开始下一个promise。例如ajax获取用户id后,再根据用户id获取用户的其他信息,比如:

function p1() {
  return new Promise(function(resolve, reject) {
    $.get("/userId", function(id) {
      resolve(id);
    });
  });
}
function p2(id) {
  return new Promise(function(resolve, reject) {
    $.get("/userInfo?id=" + id, function(info) {
      resolve(info);
    });
  });
}
p1().then(p2).then(function(info) {
  console.log(info); // 此处输出用户信息
});

这个方法的难点在于,如何衔接当前promise与后邻promise,这需要对then方法进行彻底的改造:

  this.then = function(onFulfilled) {
    return new Promise(function(resolve) {
      handle({
        onFulfilled: onFulfilled || null,
        resolve: resolve
      });
    });
  }
  function handle(deferred) {
    if(state === "pending") {
      deferreds.push(deferred);
      return;
    }
    let ret = deferred.onFulfilled(value); // 【核心3】,resolve作为onFulfilled传入的情况
    if(ret) {
      deferred.resolve(ret); // 【核心1】,onFulfilled有返回值的情况,且ret有可能为promise
    } else {
      deferred.resolve(value); // 【核心4】,onFulfilled无返回值的情况
    }
  }

为了衔接前后两个promise,让then返回了一个桥接的promise,并添加handle方法来处理onFulfilled。因为此时的onFulfilled很可能会返回一个Promise实例,所以我们需要继续改造resolve方法,用于处理参数为promise的情况。这也是最后的冲刺!

  function resolve(newValue) {
    if(newValue && (typeof newValue === "object" || typeof newValue === "function") {
      let then = newValue.then;
      then.call(newValue, resolve); // 【核心2】
      return;
    } else {
      state = "fulfilled";
      value = newValue;
      setTimeout(() => {
        deferreds.forEach((deferred) => {
          handle(deferred);
        });
      }, 0);
    }
  }

现在,我们的resolve支持传入一个Promise实例了,而且针对promise与普通值的不同,它会执行两条互不干扰的支线方法。
此时我们回到开头的例子:

function p1() {
  return new Promise(function(resolve, reject) {
    $.get("/userId", function(id) {
      resolve(id);
    });
  });
}
function p2(id) {
  return new Promise(function(resolve, reject) {
    $.get("/userInfo?id=" + id, function(info) {
      resolve(info);
    });
  });
}
p1().then(p2).then(function(info) {
  console.log(info); // 此处输出用户信息
});
  • p1异步操作获取用户id成功后,会resolve(id),因为id为普通值,所以不会触发第一个为promise所准备的支线方法,而是会去依次handle由then注册的回调

  • 现在的then方法已非吴下阿蒙,它会返回一个用于桥接的promise,但是其引用的handle还是属于p1的,在pending阶段,handle将onFulfilled(即为p2)与桥接promise的resolve组成一个对象,添加到了p1的deferreds队列中

  • p1的resolve会将deferreds队列依次handle,注意 核心1 ,此时deferred.resolve中的resolve是then返回的桥接promise的resolve,而它成功resolve后,才会继续执行后续的then。注意,此时虽然.then仍然是链式的写法,但每一次then都同步返回了一个新的promise,所以每个then的上下文是不同的(这部分我自己理解了好久)

  • 桥接promise开始resolve了(核心1 代码处),此时的ret正是一个p2的实例,所以会进行第一条支线,这就到达了 核心2 。注意,这里调用了p2实例的then方法注册了一个回调函数,而这个回调函数竟然是resolve,而这个resolve是哪个??太多resolve难免会晕,没错,这个resolve就是当前上下文的桥接promise的resolve,也就是p1.then所新生成的promise的resolve(这个我也理解了好久)

  • 而更令人容易混乱的是,p2实例的then方法也会生成一个promise实例,并且同样会将onFulfilled与resolve组成一个对象,添加到了它的deferreds队列中。不同的是,此处的onFulfilled其实是上一条所说的resolve,而此处的resolve由于p2实例没有后续的then方法已经失去了意义,不需要关心它是否会执行了

  • 代码执行到这里,p1已经resolve成功了,但是p1.then所生成的promise并没有真正resolve,因为它卡在了第一条支线,把resolve方法作为onFulfilled参数传给了p2,只有等待p2完成resolve才能继续,这就是后邻Promise能够等待前一个Promise异步操作完成再执行的奥秘了

  • 我们迎来了p2的resolve,由于是普通值,所以对deferreds队列依次handle,执行到 核心3 的时候,onFulfilled即为刚刚提到的resolve,所以我们的p1.then所生成的promise终于完成resolve了,庆祝一下!

  • 我们先不要离开p2的handle过程, 核心3 后面的代码会继续执行,由于ret为空,会deferred.resolve(value),但是实际上这是没有意义的,因为resolve只有通过then注册了回调才会发生一些事情,可p2.then()是没有后续的then的,所以可以放心的离开了!

  • 然后我们兜了一圈,又回到了p1.then所生成的promise的resolve方法,此时的newValue又称为普通值了,所以又可以对deferreds队列依次handle了!而deferreds队列中就是第二个.then所注册的普通回调函数,于是打印用户信息成功了!

  • 最后需要关注一下 核心4,这里其实是给类似最后一个.then的普通回调函数所准备的,如果ret为空,则让桥接promise直接resolve它的value,这样可以保证.then的链式的继续

  • 说的很啰嗦,但这也是我整个自己理解的过程,因为如果不啰嗦一点,确实很容易被其中的细节所迷惑。到现在为止,我们的Promise只差rejected状态没有处理了,如果上面能够很好的理解,接下来的就没什么难度了。

参考资料剖析 Promise 之基础篇

相关文章

网友评论

      本文标题:深刻理解Promise系列(二):手把手教你实现Promise(

      本文链接:https://www.haomeiwen.com/subject/dgrscxtx.html