实现同步请求_图解 Promise 实现原理(二)—— Promise 链式调用
摘要
很多同学在学习 Promise 时,知其然却不知其所以然,对其中的用法理解不了。本系列文章由浅入深逐步实现 Promise,并结合流程图、实例以及动画进行演示,达到深刻理解 Promise 用法的目的。
- 图解 Promise 实现原理(一)—— 基础实现
- 图解 Promise 实现原理(二)—— Promise 链式调用
- 图解 Promise 实现原理(三)—— Promise 原型方法实现
- 图解 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 成功时,resolve 方法将其状态置为 fulfilled ,并保存 resolve 带过来的value。然后取出 callbacks 中的对象,执行当前 Promise的 onFulfilled,返回值通过调用第二个 Promise 的 resolve 方法,传递给第二个 Promise。动画演示如下:
为了真实的看到链式调用的过程,我写一个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);
})
Demo1repl.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
通过打印出来的日志,可以看到:
- 构造 Promise-1 实例,立即执行 mackAjax('getUserId',callback);
- 调用 Promise-1 的 then 方法,注册 Promise-1 的 onFulfilled 函数。
- then 函数内部构造了一个新的 Promise实例:Promise-2。立即执行 Promise-1 的 _handle方法
- 此时 Promise-1 还是pending的状态
- Promise-1._handle 中就把注册在 Promise-1 的 onFulfilled 和 Promise-2 的 resolve 保存在 Promise-1 内部的 callbacks
- 至此当前线程执行结束。返回的是 Promise-2 的 Promise实例。
- 1s后,异步请求返回,要改变 Promise-1 的状态和结果,执行 resolve(result)
- Promise-1 的值被改变,内容为异步请求返回的结果:"getUserId异步请求耗时1s"
- Promise-1 的状态变成 fulfilled
- Promise-1 的 onFulfilled 被执行,打印出了"getUserId异步请求耗时1秒"
- 然后再调用 Promise-2.resolve
- 改变 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);
});
Demo2repl.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:
Demo3repl.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如下:
Demo4repl.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 原型方法实现zhuanlan.zhihu.com
实现同步请求_图解 Promise 实现原理(二)—— Promise 链式调用相关推荐
- 十二、Promise的学习笔记(Promise的基本使用、链式编程、all())
一.认识Promise ES6中一个非常重要和好用的特性就是Promise 但是初次接触Promise会一脸懵逼,这TM是什么东西? 看看官方或者一些文章对它的介绍和用法,也是一头雾水. Promis ...
- JS 通过 Promise 实现延时执行,支持链式调用
JS 通过 Promise 实现延时执行,支持链式调用 延时执行 sleep 版本一 同步执行 异步方式 sleep 版本二 支持链式调用 版本一 版本二 参考资料 延时执行 定义sleep函数 sl ...
- 彻底掌握 Promise-原生Promise的实现(二) Promise的链式调用
在 彻底掌握 Promise- 原生 Promise 的实现(一) 已经初步实现了 promise 的简易版本,这距离实现完整版本的 Promise 还有一段距离,下面我们将在简易版本的基础上增加 P ...
- promise的链式调用
//new一个promise对象并在里面执行第一个异步方法 var promise = new Promise(function(resolve,reject){axios({method: 'pos ...
- yarn的组成部分_图解YARN工作原理
YARN 即MapReduce V2版本.相比MapReduce V1 它有很多优点: 1. 分散了jobTracker 的任务.资源管理任务由资源管理器负责,作业启动.运行和监测任务由分布在集群节点 ...
- mysql新增列并同时增加数据_图解MySQL | 「原理解析」 MySQL 为表添加列 是怎么立刻完成的...
原创作者:图解MySQL 在上一期图解 图解MySQL | MySQL DDL为什么成本高?中,我们介绍了: 传统情况下,为表添加列需要对表进行重建 腾讯团队为 MySQL 引入了 Instant A ...
- dispatcherservlet发送2次请求_[Java] SpringMVC工作原理之一:DispatcherServlet
一.DispatcherServlet 处理流程 在整个 Spring MVC 框架中,DispatcherServlet 处于核心位置,它负责协调和组织不同组件完成请求处理并返回响应工作.在看 Di ...
- mysql数据丢失_图解MySQL | 「原理解析」 MySQL使用固定的server_id导致数据丢失
原创作者:爱可生开源社区 本文我们来看一个场景,两台MySQL实例使用主从复制,当master故障,触发高可用切换,新master上线后,通过备份重建旧master并建立复制后,数据发生丢失. 以下我 ...
- 红黑树与平衡二叉树_图解“红黑树”原理,一看就明白!
" 学过数据结构都知道二叉树的概念,而又有多种比较常见的二叉树类型,比如完全二叉树.满二叉树.二叉搜索树.均衡二叉树.完美二叉树等. 图片来自 Pexels 今天我们要说的红黑树就是就是一棵 ...
最新文章
- win32 API 遍历一个目录下的文件
- MySQL Merge引擎实现分表
- [PHP] 算法-数组重复数字统计的PHP实现
- Python最佳代码实践:性能、内存和可用性!
- C++虚继承(八) --- 虚继承与继承的差异
- php winform通信,C# Winform 通过Socket实现客户端和服务端TCP通信
- Script:收集数据库中用户的角色和表空间等信息
- JavaSE基本语法练习题(下)
- 手机号段归属地数据库
- Little happiness matters?蒙牛新广告语英文翻译引争议
- SPSS基本数据处理(二)
- 编写程序:实现输出100以内质数的功能
- 既有禀赋上的自然延展:中国移动咪咕进军元宇宙的底层逻辑
- 江苏事业单位计算机类考试题型,江苏事业单位统考各岗位考试类型和题目分值一览!...
- windows设置hosts
- 西门子免授权CNC数控系统数据采集c#、C、python都支持,可支持再各种操作系统上运行,无须西门子OPC,支持828D	840dsl 808 802dsl 840d 810d 西门子数控DNC程序
- 【数据结构】悬空指针和野指针
- mysql中avg函数如果有空值_对于 AVG 函数,如果列中所有行的值都是 null,那么 AVG 函数返回的值是( )...
- HTML form表单添加enctype属性后获取不到input值
- JD青龙面板任务代理池部署与使用教程
热门文章
- 直播丨Oracle 12cR2 ADG LGWR Library Cache案例分享
- 3场直播丨达梦DM8数据库安装部署初体验、新基建下的国产数据库应用和发展趋势、Oracle外部表创建与使用...
- 【假期重磅福利】更新三个Oracle系列课程,共153课时,最低免费获取
- 记录一次数据同步到数据仓库的架构与实践
- 带你读Paper丨分析ViT尚存问题和相对应的解决方案
- #开工新姿势#开启一年新征程,云社区叫你来充电啦!
- 14天1000+大集群滚动升级,银行柜台竟然毫无感觉
- 案例展示自定义C函数的实现过程
- 【华为云技术分享】华为专家亲述:如何转型搞 AI?
- pandas使用笔记(一)导入,查看,读取数据