Promise-Polyfill源码解析(2)

在上篇文章Promise-Polyfill源码解析(1)详细分析了Promise构造函数部分的源码,本篇我们继续分析剩下的源码。
本篇我们重点分析then方法,让我们回忆下then方法的使用方式:首先这个方法属于每个Promise对象,这说明then方法应该定义在Promise的原型链上;然后这个方法接收两个回调函数,如果Promsie的状态为已完成,则执行第一个回调,状态为被拒绝,则执行第二个回调,这个说明then方法会等待Promise状态改变才会去执行回调;最后then方法可以链式调用,如下:

Promise.resolve().then(function() {
  // ...
}, function() {
  // ...
}).then(function() {
  // ...
}, function() {
  // ...
});

了解了以上,我们来看then方法的源码:

Promise.prototype.then = function(onFulfilled, onRejected) {
  // @ts-ignore
  var prom = new this.constructor(noop);

  handle(this, new Handler(onFulfilled, onRejected, prom));
  return prom;
};

正如我们所猜想的,then方法定义在Promise的构造函数上,每个Promise对象可以共享该方法。其接收两个参数onFulfilled、onRejected。具体实现也非常简洁,只有三行代码,先来看第一行:

var prom = new this.constructor(noop);

这句代码用new操作符实例化了一个对象,并保存在prom变量中。new操作符的右边一定是个构造函数,this指向当前Promise对象,其constructor属性指向构造函数,所以this.constructor指向Promise构造函数。我们知道,Promise构造函数的参数为一个函数,这里传入了noop,noop是什么?我们找到其定义:

function noop() {}

我们发现noop只是个空函数。再来看最后一行代码:

return prom;

返回了prom对象,也就是说,then方法最后返回了一个Promise对象,这也就是then方法可以链式调用的原因所在!
有个疑问,为什么不直接返回this,而是返回新创建的Promise对象呢?其实是因为Promise的状态改变时单向的,且只能改变一次。
然后重点来看下第二行代码:

handle(this, new Handler(onFulfilled, onRejected, prom));

调用了handle函数,先不管handle做了什么,我们先关注其第二个实参:

new Handler(onFulfilled, onRejected, prom)

其实例化了Handler对象,参数为then方法的两个参数和prom对象,我们来看下其具体实现:

/**
 * @constructor
 */
function Handler(onFulfilled, onRejected, promise) {
  this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
  this.onRejected = typeof onRejected === 'function' ? onRejected : null;
  this.promise = promise;
}

Handler构造函数将传入的参数分别赋值给实例对象的onFulfilled、onRejected、promise属性,其中对onFulfilled和onRejected做了处理,若不是函数类型,则赋值为null。这说明,我们传入给then方法的两个参数可以不为函数类型,其内部会调整为null。
明白了第二个参数,我们来看handle函数具体做了什么:

function handle(self, deferred) {
  while (self._state === 3) {
    self = self._value;
  }
  if (self._state === 0) {
    self._deferreds.push(deferred);
    return;
  }
  self._handled = true;
  Promise._immediateFn(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    if (cb === null) {
      (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
      return;
    }
    var ret;
    try {
      ret = cb(self._value);
    } catch (e) {
      reject(deferred.promise, e);
      return;
    }
    resolve(deferred.promise, ret);
  });
}

首先是一个while循环:

 while (self._state === 3) {
    self = self._value;
  }

self指的是当前Promise对象,如果self._state的值为3,则将self._value赋值给self。我们在上篇文章分析过,_state属性值为3,则说明_value值为一个Promise对象。那么这个循环的结果就是,直到_value属性值不为Promise对象,为什么要这么处理呢?我们来看下规范是怎么说的:
如果 x 为 Promise,则使promise接收x的状态

  • 如果 x 处于pendding,promise需要保持为pendding状态直至x被解决或拒绝
  • 如果 x 处于fulFilled,用相同的值执行 promise
  • 如果 x 处于rejected,用相同的据因拒绝 promise
    总结起来就是,如果_value属性值为Promise对象,则结果取决于嵌套最内层Promise的状态。
    接下来是一个条件判断:
 if (self._state === 0) {
    self._deferreds.push(deferred);
    return;
  }

如果self._state属性为0,则将deferred压入self._deferreds数组,并结束此次函数调用。其中deferred为传入的Handler实例对象,我们在上篇里分析过,_state属性值为0表示Promise的状态为pendding,我们可以猜测到,状态为pedding,也就是Promise的状态并未改变,then方法不知道要执行哪个回调,所在要先保存。那么为什么是保存在一个数组里,而不是保存在一个变量里,难道有很多个?其实还真可能有很多个,因为then方法可以被多次调用:


image.png

可以看到,每个then方法的回调都被执行了。
再来看下面的代码:

self._handled = true;

上篇文章也分析过,_handled属性用来标记Promise是否被处理,这里将其赋值为true,说明当前Promise对象已经被处理了。
最后来看最后一段代码:

Promise._immediateFn(function() {
   ...
});

调用了Promise._immediateFn方法,并传入了一个回调函数。先来看Promise._immediateFn的定义:

// Use polyfill for setImmediate for performance gains
Promise._immediateFn =
  (typeof setImmediate === 'function' &&
    function(fn) {
      setImmediate(fn);
    }) ||
  function(fn) {
    setTimeoutFunc(fn, 0);
  };

这里判断setImmediate是否是函数类型,成里则赋值为function(fn) { setImmediate(fn) },否则赋值为function(fn) { setTimeoutFunc(fn, 0) },其中setTimeoutFunc是setTimeout的别名:

var setTimeoutFunc = setTimeout;

setImmediate是Node.js里的global对象的属性,而setTimeout是浏览器环境里window对象的属性,所以Promise._immediate是兼容两个环境所做处理的代码。为什么要再包一层闭包呢?应该是兼容参数的数量。
到这我们也明白了,then方法的回调是异步执行,其实更具体是在micro队列中,这里我们就不展开了。
回到Promise._immediateFn的回调参数:

var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;

上篇文章分析过,self._state属性值为1表示Promise的状态为已完成,为2表示状态为被决绝。那么这句代码的意思是,根据Promise的状态,将then方法的完成回调或决绝回调赋值给cb变量。
再来看下面的条件判断:

if (cb === null) {
  (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
  return;
}

cb变量为null,也就是我们传入给then方法的参数不是函数类型,这里会根据Promise的状态执行resolve或reject函数,并结束此次调用。注意传入的参数,deferred.promise和self._value,也就是说,用Promise的值去改变在then方法内创建的Promise对象的状态。总结起来就是,若then方法未传入对应的回调,那么Promise的值会被传递到下一次then方法中:


image.png

再来看最后一段代码:

var ret;
try {
  ret = cb(self._value);
} catch (e) {
  reject(deferred.promise, e);
  return;
}
resolve(deferred.promise, ret);

忽略try..catch,核心是这样的:

var ret = cb(self._value);
resolve(deferred.promise, ret);

将self._value作为参数,调用cb函数,返回值保存在ret变量中,再以ret变量为参数调用resolve函数。这里的意思就是,将cb函数的返回值作为Promise的值传递给下一个then方法:


image.png

当然,若抛出异常,则将原因作为Promise的值,传递给下一个then方法:

reject(deferred.promise, e);
return;

至此,Promise源码的核心部分已经分析完了,我们可以发现,阅读源码可以了解Promise的内部的工作机制,当出现问题时,我们也能快速定位原因。鼓励大家去阅读源码!
当然还有catch、all、race等方法,将在下一篇文章继续分析。

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

推荐阅读更多精彩内容

  • title: promise总结 总结在前 前言 下文类似 Promise#then、Promise#resolv...
    JyLie阅读 12,234评论 1 21
  • 本文适用的读者 本文写给有一定Promise使用经验的人,如果你还没有使用过Promise,这篇文章可能不适合你,...
    HZ充电大喵阅读 7,301评论 6 19
  • 官方中文版原文链接 感谢社区中各位的大力支持,译者再次奉上一点点福利:阿里云产品券,享受所有官网优惠,并抽取幸运大...
    HetfieldJoe阅读 11,025评论 26 95
  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,704评论 1 56
  • 高中生心理咨询 引言 引入:自己以前去过青岛 青岛市有心理健康教育的教材,南师大心理学院k12教材。江苏省三套权威...
    少儿创客阅读 1,361评论 0 6