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

:经过作者的进一步学习,觉得有更好的串行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 之基础篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,698评论 1 56
  • 00、前言Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区...
    夜幕小草阅读 2,128评论 0 12
  • 你不知道JS:异步 第三章:Promises 在第二章,我们指出了采用回调来表达异步和管理并发时的两种主要不足:缺...
    purple_force阅读 2,052评论 0 4
  • Promiese 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Pr...
    雨飞飞雨阅读 3,348评论 0 19
  • 官方中文版原文链接 感谢社区中各位的大力支持,译者再次奉上一点点福利:阿里云产品券,享受所有官网优惠,并抽取幸运大...
    HetfieldJoe阅读 11,023评论 26 95