摘要

很多同学在学习 Promise 时,知其然却不知其所以然,对其中的用法理解不了。本系列文章由浅入深逐步实现 Promise,并结合流程图、实例以及动画进行演示,达到深刻理解 Promise 用法的目的。

  1. 图解 Promise 实现原理(一)—— 基础实现
  2. 图解 Promise 实现原理(二)—— Promise 链式调用
  3. 图解 Promise 实现原理(三)—— Promise 原型方法实现
  4. 图解 Promise 实现原理(四)—— Promise 静态方法实现

前言

上一节中,实现了 Promise 的基础版本:

//极简的实现+链式调用+延迟机制+状态
class Promise {callbacks = [];state = 'pending';//增加状态value = null;//保存结果constructor(fn) {fn(this._resolve.bind(this));}then(onFulfilled) {if (this.state === 'pending') {//在resolve之前,跟之前逻辑一样,添加到callbacks中this.callbacks.push(onFulfilled);} else {//在resolve之后,直接执行回调,返回结果了onFulfilled(this.value);}return this;}_resolve(value) {this.state = 'fulfilled';//改变状态this.value = value;//保存结果this.callbacks.forEach(fn => fn(value));}
}

但链式调用,只是在 then 方法中 return 了 this,使得 Promise 实例可以多次调用 then 方法,但因为是同一个实例,调用再多次 then 也只能返回相同的一个结果,通常我们希望的链式调用是这样的:

//使用Promise
function getUserId(url) {return new Promise(function (resolve) {//异步请求http.get(url, function (id) {resolve(id)})})
}
getUserId('some_url').then(function (id) {//do somethingreturn getNameById(id);
}).then(function (name) {//do somethingreturn getCourseByName(name);
}).then(function (course) {//do somethingreturn getCourseDetailByCourse(course);
}).then(function (courseDetail) {//do something
});

每个 then 注册的 onFulfilled 都返回了不同的结果,层层递进,很明显在 then 方法中 return this 不能达到这个效果。引入真正的链式调用,then 返回的一定是一个新的Promise实例

真正的链式 Promise 是指在当前 Promise 达到 fulfilled 状态后,即开始进行下一个 Promise(后邻 Promise)。那么我们如何衔接当前 Promise 和后邻 Promise 呢?(这是理解 Promise 的难点,我们会通过动画演示这个过程)。

链式调用的实现

先看下实现源码:

//完整的实现
class Promise {callbacks = [];state = 'pending';//增加状态value = null;//保存结果constructor(fn) {fn(this._resolve.bind(this));}then(onFulfilled) {return new Promise(resolve => {this._handle({onFulfilled: onFulfilled || null,resolve: resolve});});}_handle(callback) {if (this.state === 'pending') {this.callbacks.push(callback);return;}//如果then中没有传递任何东西if (!callback.onFulfilled) {callback.resolve(this.value);return;}var ret = callback.onFulfilled(this.value);callback.resolve(ret);}_resolve(value) {this.state = 'fulfilled';//改变状态this.value = value;//保存结果this.callbacks.forEach(callback => this._handle(callback));}
}

由上面的实现,我们可以看到:

  • then 方法中,创建并返回了新的 Promise 实例,这是串行Promise的基础,是实现真正链式调用的根本
  • then 方法传入的形参 onFulfilled 以及创建新 Promise 实例时传入的 resolve 放在一起,被push到当前 Promise 的 callbacks 队列中,这是衔接当前 Promise 和后邻 Promise 的关键所在
  • 根据规范,onFulfilled 是可以为空的,为空时不调用 onFulfilled

看下动画演示:

Promise 链式调用演示动画

当第一个 Promise 成功时,resolve 方法将其状态置为 fulfilled ,并保存 resolve 带过来的value。然后取出 callbacks 中的对象,执行当前 Promise的 onFulfilled,返回值通过调用第二个 Promise 的 resolve 方法,传递给第二个 Promise。动画演示如下:

Promise 链式调用 fulfilled

为了真实的看到链式调用的过程,我写一个mockAjax函数,用来模拟异步请求:

/*** 模拟异步请求* @param {*} url  请求的URL* @param {*} s  指定该请求的耗时,即多久之后请求会返回。单位秒* @param {*} callback 请求返回后的回调函数*/
const mockAjax = (url, s, callback) => {setTimeout(() => {callback(url + '异步请求耗时' + s + '秒');}, 1000 * s)
}

除此之外,我给 Promise 的源码加上了日志输出并增加了构造顺序标识,可以清楚的看到构造以及执行过程:

//Demo1
new Promise(resolve => {mockAjax('getUserId', 1, function (result) {resolve(result);})
}).then(result => {console.log(result);
})

Demo1​repl.it

执行结果如下:

[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId异步请求耗时1秒
[Promse-1]:_handle state= fulfilled
getUserId异步请求耗时1秒
[Promse-2]:_resolve
[Promse-2]:_resolve value= undefined

通过打印出来的日志,可以看到:

  1. 构造 Promise-1 实例,立即执行 mackAjax('getUserId',callback);
  2. 调用 Promise-1 的 then 方法,注册 Promise-1 的 onFulfilled 函数。
  3. then 函数内部构造了一个新的 Promise实例:Promise-2。立即执行 Promise-1 的 _handle方法
  4. 此时 Promise-1 还是pending的状态
  5. Promise-1._handle 中就把注册在 Promise-1 的 onFulfilled 和 Promise-2 的 resolve 保存在 Promise-1 内部的 callbacks
  6. 至此当前线程执行结束。返回的是 Promise-2 的 Promise实例。
  7. 1s后,异步请求返回,要改变 Promise-1 的状态和结果,执行 resolve(result)
  8. Promise-1 的值被改变,内容为异步请求返回的结果:"getUserId异步请求耗时1s"
  9. Promise-1 的状态变成 fulfilled
  10. Promise-1 的 onFulfilled 被执行,打印出了"getUserId异步请求耗时1秒"
  11. 然后再调用 Promise-2.resolve
  12. 改变 Promise-2 的值和状态,因为 Promise-1 的 onFulfilled 没有返回值,所以 Promise-2的值为undefined

上例中,如果把异步的请求改成同步会是什么的效果?

new Promise(resolve => {resolve('getUserId同步请求');
}).then(result => {console.log(result);
});//打印日志
[Promse-1]:constructor
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId同步请求
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= fulfilled
getUserId同步请求
[Promse-2]:_resolve
[Promse-2]:_resolve value= undefined
=> Promise {callbacks: [],name: 'Promse-2',state: 'fulfilled',value: undefined }

感兴趣的可以自己去分析一下。

链式调用真正的意义

执行当前 Promise 的 onFulfilled 时,返回值通过调用第二个 Promise 的 resolve 方法,传递给第二个 Promise,作为第二个 Promise 的值。于是我们考虑如下Demo:

//Demo2
new Promise(resolve => {mockAjax('getUserId', 1, function (result) {resolve(result);})
}).then(result => {console.log(result);//对result进行第一层加工let exResult = '前缀:' + result;return exResult;
}).then(exResult => {console.log(exResult);
});

Demo2​repl.it

我们加了一层 then,来看下执行的结果:

[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-2]:then
[Promse-3]:constructor
[Promse-2]:_handle state= pending
[Promse-2]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-3', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId异步请求耗时1秒
[Promse-1]:_handle state= fulfilled
getUserId异步请求耗时1秒
[Promse-2]:_resolve
[Promse-2]:_resolve value= 前缀:getUserId异步请求耗时1秒
[Promse-2]:_handle state= fulfilled
前缀:getUserId异步请求耗时1秒
[Promse-3]:_resolve
[Promse-3]:_resolve value= undefined:

链式调用可以无限的写下去,上一级 onFulfilled return 的值,会变成下一级 onFulfilled 的结果。可以参考Demo3:

Demo3​repl.it

我们很容易发现,上述 Demo3 中只有第一个是异步请求,后面都是同步的,我们完全没有必要这么链式的实现。如下一样能得到我们想要的三个结果: 分别打印出来的值。

//等价于 Demo3
new Promise(resolve => {mockAjax('getUserId', 1, function (result) {resolve(result);})
}).then(result => {console.log(result);//对result进行第一层加工let exResult = '前缀:' + result;console.log(exResult);let finalResult = exResult + ':后缀';console.log(finalResult);
});

那链式调用真正的意义在哪里呢?

刚才演示的都是 onFulfilled 返回值是 value 的情况,如果是一个 Promise 呢?是不是就可以通过 onFulfilled,由使用 Promise 的开发者决定后续 Promise 的状态。

于是在 _resolve 中增加对前一个 Promise onFulfilled 返回值的判断:

    _resolve(value) {if (value && (typeof value === 'object' || typeof value === 'function')) {var then = value.then;if (typeof then === 'function') {then.call(value, this._resolve.bind(this));return;}}this.state = 'fulfilled';//改变状态this.value = value;//保存结果this.callbacks.forEach(callback => this._handle(callback));}

从代码上看,它是对 resolve 中的值作了一个特殊的判断,判断 resolve 的值是否为 Promise实例,如果是 Promise 实例,那么就把当前 Promise 实例的状态改变接口重新注册到 resolve 的值对应的 Promise 的 onFulfilled 中,也就是说当前 Promise 实例的状态要依赖 resolve 的值的 Promise 实例的状态。

//Demo4
const pUserId = new Promise(resolve => {mockAjax('getUserId', 1, function (result) {resolve(result);})
})
const pUserName = new Promise(resolve => {mockAjax('getUserName', 2, function (result) {resolve(result);})
})pUserId.then(id => {console.log(id)return pUserName
}).then(name => {console.log(name)
})

Demo如下:

Demo4​repl.it

执行的结果如下:

[Promse-1]:constructor
[Promse-2]:constructor
[Promse-1]:then
[Promse-3]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-3]:then
[Promse-4]:constructor
[Promse-3]:_handle state= pending
[Promse-3]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-4', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId异步请求耗时1秒
[Promse-1]:_handle state= fulfilled
getUserId异步请求耗时1秒
[Promse-3]:_resolve
[Promse-3]:_resolve value= Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-2]:then
[Promse-5]:constructor
[Promse-2]:_handle state= pending
[Promse-2]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-2]:_resolve
[Promse-2]:_resolve value= getUserName异步请求耗时2秒
[Promse-2]:_handle state= fulfilled
[Promse-3]:_resolve
[Promse-3]:_resolve value= getUserName异步请求耗时2秒
[Promse-3]:_handle state= fulfilled
getUserName异步请求耗时2秒
[Promse-4]:_resolve
[Promse-4]:_resolve value= undefined
[Promse-5]:_resolve
[Promse-5]:_resolve value= undefined

一样的,我做了一个演示动画,还原了这个过程:

Promise 真正的链式调用

至此,就实现了 Promise 链式调用的全部内容。链式调用是 Promise 难点,更是重点。一定要通过实例还有动画,深刻体会。下一节介绍 Promise 其它原型方法的实现:

孔家少爷:图解 Promise 实现原理(三)—— Promise 原型方法实现​zhuanlan.zhihu.com

实现同步请求_图解 Promise 实现原理(二)—— Promise 链式调用相关推荐

  1. 十二、Promise的学习笔记(Promise的基本使用、链式编程、all())

    一.认识Promise ES6中一个非常重要和好用的特性就是Promise 但是初次接触Promise会一脸懵逼,这TM是什么东西? 看看官方或者一些文章对它的介绍和用法,也是一头雾水. Promis ...

  2. JS 通过 Promise 实现延时执行,支持链式调用

    JS 通过 Promise 实现延时执行,支持链式调用 延时执行 sleep 版本一 同步执行 异步方式 sleep 版本二 支持链式调用 版本一 版本二 参考资料 延时执行 定义sleep函数 sl ...

  3. 彻底掌握 Promise-原生Promise的实现(二) Promise的链式调用

    在 彻底掌握 Promise- 原生 Promise 的实现(一) 已经初步实现了 promise 的简易版本,这距离实现完整版本的 Promise 还有一段距离,下面我们将在简易版本的基础上增加 P ...

  4. promise的链式调用

    //new一个promise对象并在里面执行第一个异步方法 var promise = new Promise(function(resolve,reject){axios({method: 'pos ...

  5. yarn的组成部分_图解YARN工作原理

    YARN 即MapReduce V2版本.相比MapReduce V1 它有很多优点: 1. 分散了jobTracker 的任务.资源管理任务由资源管理器负责,作业启动.运行和监测任务由分布在集群节点 ...

  6. mysql新增列并同时增加数据_图解MySQL | 「原理解析」 MySQL 为表添加列 是怎么立刻完成的...

    原创作者:图解MySQL 在上一期图解 图解MySQL | MySQL DDL为什么成本高?中,我们介绍了: 传统情况下,为表添加列需要对表进行重建 腾讯团队为 MySQL 引入了 Instant A ...

  7. dispatcherservlet发送2次请求_[Java] SpringMVC工作原理之一:DispatcherServlet

    一.DispatcherServlet 处理流程 在整个 Spring MVC 框架中,DispatcherServlet 处于核心位置,它负责协调和组织不同组件完成请求处理并返回响应工作.在看 Di ...

  8. mysql数据丢失_图解MySQL | 「原理解析」 MySQL使用固定的server_id导致数据丢失

    原创作者:爱可生开源社区 本文我们来看一个场景,两台MySQL实例使用主从复制,当master故障,触发高可用切换,新master上线后,通过备份重建旧master并建立复制后,数据发生丢失. 以下我 ...

  9. 红黑树与平衡二叉树_图解“红黑树”原理,一看就明白!

    " 学过数据结构都知道二叉树的概念,而又有多种比较常见的二叉树类型,比如完全二叉树.满二叉树.二叉搜索树.均衡二叉树.完美二叉树等. 图片来自 Pexels 今天我们要说的红黑树就是就是一棵 ...

最新文章

  1. win32 API 遍历一个目录下的文件
  2. MySQL Merge引擎实现分表
  3. [PHP] 算法-数组重复数字统计的PHP实现
  4. Python最佳代码实践:性能、内存和可用性!
  5. C++虚继承(八) --- 虚继承与继承的差异
  6. php winform通信,C# Winform 通过Socket实现客户端和服务端TCP通信
  7. Script:收集数据库中用户的角色和表空间等信息
  8. JavaSE基本语法练习题(下)
  9. 手机号段归属地数据库
  10. Little happiness matters?蒙牛新广告语英文翻译引争议
  11. SPSS基本数据处理(二)
  12. 编写程序:实现输出100以内质数的功能
  13. 既有禀赋上的自然延展:中国移动咪咕进军元宇宙的底层逻辑
  14. 江苏事业单位计算机类考试题型,江苏事业单位统考各岗位考试类型和题目分值一览!...
  15. windows设置hosts
  16. 西门子免授权CNC数控系统数据采集c#、C、python都支持,可支持再各种操作系统上运行,无须西门子OPC,支持828D 840dsl 808 802dsl 840d 810d 西门子数控DNC程序
  17. 【数据结构】悬空指针和野指针
  18. mysql中avg函数如果有空值_对于 AVG 函数,如果列中所有行的值都是 null,那么 AVG 函数返回的值是( )...
  19. HTML form表单添加enctype属性后获取不到input值
  20. JD青龙面板任务代理池部署与使用教程

热门文章

  1. 直播丨Oracle 12cR2 ADG LGWR Library Cache案例分享
  2. 3场直播丨达梦DM8数据库安装部署初体验、新基建下的国产数据库应用和发展趋势、Oracle外部表创建与使用...
  3. 【假期重磅福利】更新三个Oracle系列课程,共153课时,最低免费获取
  4. 记录一次数据同步到数据仓库的架构与实践
  5. 带你读Paper丨分析ViT尚存问题和相对应的解决方案
  6. #开工新姿势#开启一年新征程,云社区叫你来充电啦!
  7. 14天1000+大集群滚动升级,银行柜台竟然毫无感觉
  8. 案例展示自定义C函数的实现过程
  9. 【华为云技术分享】华为专家亲述:如何转型搞 AI?
  10. pandas使用笔记(一)导入,查看,读取数据