在上篇文章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-ignorevar 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方法可以被多次调用:

可以看到,每个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方法中:

再来看最后一段代码:

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方法:

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

reject(deferred.promise, e);
return;
复制代码

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

Promise-Polyfill源码解析(2)相关推荐

  1. bind函数polyfill源码解析

    准备知识 使用new来调用函数会自动执行下面的操作: 创建一个全新的对象 这个新对象会被执行原型连接 这个新对象会绑定到函数调用的this 如果函数没有返回其他对象,那么new表达式中的函数调用会自动 ...

  2. netty依赖_Netty系列之源码解析(一)

    接下来的时间灯塔君持续更新Netty系列一共九篇 当前:Netty 源码解析(一)开始 Netty 源码解析(二): Netty 的 Channel Netty 源码解析(三): Netty 的 Fu ...

  3. Promise源码解析

    Promise源码解析 纸上得来终觉浅,绝知此事要躬行.之前只是很浅显的知道Promise的用法,也大概猜测到它的内部是如何实现的.但是总是有一种不深究一下就不踏实的感觉.于是从npm上获得早期的Pr ...

  4. babel源码解析之(@babel/preset-env)

    前言 还记得之前写过一篇文章:babel源码解析一,里面把babel的整个流程跑了一遍,最后还自定义了一个插件用来转换"箭头函数",通过前面的源码解析我们知道,preset其实就是 ...

  5. axios网络请求框架源码解析

    早期axios0.1.0版本做了对IE浏览器与包含XmlHttpRequest的浏览器的支持.并且做了对请求参数拼接.Json对象序列化等基本功能. 到0.19.0版本时,内部请求已经变为了在Node ...

  6. 【Vue.js源码解析 一】-- 响应式原理

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 课程目标 Vue.js 的静态成员和实例成员初始化过程 首次渲染的过程 数据响应式原理 – 最核心的特性之一 准备工作 ...

  7. Netty 源码解析系列-服务端启动流程解析

    netty源码解析系列 Netty 源码解析系列-服务端启动流程解析 Netty 源码解析系列-客户端连接接入及读I/O解析 五分钟就能看懂pipeline模型 -Netty 源码解析 1.服务端启动 ...

  8. webpack那些事:浅入深出-源码解析构建优化

    基础知识回顾 入口(entry) module.exports = {entry: './path/to/my/entry/file.js' }; //或者 module.exports = {ent ...

  9. 前端单页路由《stateman》源码解析

    <stateman>是波神的一个超级轻量的单页路由,拜读之后写写自己的小总结. stateman的github地址 github.com/leeluolee/s- 简单使用 以下文章全部以 ...

最新文章

  1. Supervisor使用详解
  2. 小程序云开发获取手机号完整代码 云函数中网络请求第三方接口
  3. python编程零基础-如何零基础入门Python编程?
  4. 【全栈项目上线(vue+node+mongodb)】06.nodejs服务上线(生产环境前后分离的vue项目中怎么解决跨域问题)...
  5. php保存成乱序,PHP实现断点续传乱序合并文件的方法
  6. fast.ai_使用fast.ai自组织地图—步骤4:使用Fast.ai DataBunch处理非监督数据
  7. openstack架构详解图_英特尔顶级技术专家合力缔造精品:Linux开源网络全栈详解...
  8. 苹果新的编程语言 Swift 语言进阶(二)--基本数据类型
  9. .net framework 4.0 0xc8000247错误解决
  10. Dijkstra算法(单源最短路)
  11. oracle18c如何创建hr用户,Oracle18c创建不带C##的用户
  12. CVPR 2021 TrafficQA
  13. 互联网和物联网的区别,你有必要了解一下
  14. mysql8 启动报错:Error while setting value ‘STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DI
  15. 机器人轨迹规划(熊友伦)
  16. 首域微交易分析师一对一服务
  17. redis的lru原理_Redis的LRU机制介绍
  18. C语言动态申请内存空间之malloc(),calloc(),realloc()函数
  19. 用C实现汉诺塔问题(体现每次移动时方块的位置变化和计算共移动多少次)
  20. 利用米筐量化回测平台实行量化炒股

热门文章

  1. 在JavaScript中使用json.js:访问JSON编码的某个值
  2. obj.toSource()
  3. 第七天学习Java的笔记(IDEA环境配置)
  4. pep8 python 编码规范_Python合集之Python语法特点(三)
  5. 遇到tensorflow has no attribute 问题
  6. 安装 | R2021a链接及Matlab运行图
  7. mac安装nvm及换源及node安装切换
  8. 30 多年的软件经验,总结出 10 个编写出更好代码的技巧
  9. 日志库EasyLogging++学习系列(10)—— 日志文件滚动
  10. HTML5制作斑马线表格,JavaScript实现的斑马线表格效果【隔行变色】