一,前言
上一篇,实现 Promise 对返回值 x 各种情况的分析和处理,主要涉及以下几个点:
- 回顾了相关的 Promise A+ 规范内容;
- 根据 Promise A+ 规范描述和要求,实现了核心解析方法:resolvePromise;
本篇,继续对 Promise 进行完善并通过 promise-aplus-tests 测试;
二,当前 Promise 的问题
当前版本 Promise 源码,已基本实现了 Promise A+ 规范中的全部要求;
但在实际使用中,以下两种情况仍与原生 Promise 表现不一致:
- 1,Promise 直接 resolve 一个 Promise;
- 2,Promise 的 then 中方法返回 promise,这个 promise 又继续 resolve 了一个 promise;
备注
- 有人会说:1 和 2 两种情况好像是一个事啊?都是 resolve 中又返回了 promise;
- 先说一下区别,后边详细分析:
- resolve 中直接返回的 promise,对应源码中的 value;
- then 中返回的 promise 中的 resolve 中的 promise,对应源码中的 y;
三,情况 1:Promise 直接 resolve 一个 Promise
1,提出问题
如果在 Promise 中的 resolve 传入一个 Promise,结果是怎样的?
new Promise((resolve,reject)=>{
resolve(new Promise((resolve,reject)=>{
resolve(100)
}))
}).then(data=>{
console.log(data)
})
正常情况下 resolve(100),then 成功回调中的 data 就是 100,所以 data 就是 promise,就是 100;
2,测试原生的 Promise
原生 Promise 返回 100,与预期相符
3,测试手写的 Promise
与原生 Promise 表现不一致,返回 DULFILLED 成功态 promise 对象:
Promise {
state: 'DULFILLED',
value: 100,
reason: undefined,
onResolvedCallbacks: [],
onRejectedCallbacks: []
}
4,问题分析
对比手写的 Promise 和原生 Promise 的执行结果,问题出在哪?
new Promise((resolve,reject)=>{
resolve(new Promise((resolve,reject)=>{ // 这个 promise 就是 value
resolve(100) // 成功结果
}))
}).then(data=>{ // 这个 data 就是上边的 promise,所以是 Pending 态
console.log(data)
})
分析代码的执行流程:
- 通过 new Promise 创建 promise1 实例,executor1 执行器函数被立即执行;
- 在 executor1 中,执行了
resolve(new Promise)
,又创建 promise2 实例,executor2 被立即执行; - 在 executor2 中,由于没有异步操作,所以继续执行了
resolve(100)
; - Promise 源码处理:在 resolve 方法中,存储了 value 值为 100、设置了 promise2 状态为成功态、执行了成功回调(未收集的空数组);
- 调用
then
方法; - Promise 源码处理:then 方法内部创建 promise3,此时,promise2 为 DULFILLED 成功态,执行 onFulfilled 成功回调处理,并将 promise2 作为 data 传给 then的成功回调,也就是 data
这就导致了返回结果是一个 DULFILLED 成功态的 promise 对象;
问题原因:
源码中没有考虑到 resolve(value) 中 value 有可能是 promise 的情况;
const reslove = (value) =>{
if(this.state === PENDING){
this.value = value // value 有可能是一个 promise
this.state = DULFILLED;
this.onResolvedCallbacks.forEach(fn=>fn())
}
}
解决方案:
添加判断,如果 value 是一个 promise,调用 then 让其执行;
5,代码实现
判断value 是 Promise 类型(必须是自己 Promise 才可以),调用 then 并传入resolve、reject(成功将会调用 resolve;失败将会调用 reject),返回最终 promise 的执行结果;
const reslove = (value) => {
// value 是自己实现的 Promise,就调用 then,返回最终结果
if(value instanceof Promise){
return value.then(reslove, reject)
}
if (this.state === PENDING) {
this.value = value
this.state = DULFILLED;
this.onResolvedCallbacks.forEach(fn => fn())
}
}
这样,reslove 了一个 promise 时,在源码 reslove 方法中,就会调用它的 then 方法,返回 promise 执行后的结果;
备注:Promise A+ 规范中未对此情况进行说明;
四,情况 2:then 返回的 promise 中,resolve 了 promise(有点儿绕嘴)
1,提出问题
在上一篇中,当 Promise.then
返回 promise 时,核心逻辑resolvePromise
如下:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('发生错误'))
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then;
if(typeof then === 'function'){
// 调用 promise 的 then 方法
then.call(x, y => {
resolve(y) // 更新 promise2 的状态
}, r => {
reject(r)
});
}else{
resolve(x)
}
} catch (e) {
reject(e);
}
} else {
resolve(x)
}
}
- then 中方法的返回值 y 有可能是 promise,形成 promise 的嵌套;
情况 1 已经处理了 resolve 中是 promise 的情况,这里还会有问题吗?
一般是没有问题的,但这里的 promise 有可能是其他人实现的 Promise;
(情况 1 中的 Promise 必须是自己实现的)
2,测试原生的 Promise
let promise2 = new Promise((resolve, reject) => {
resolve(200)
}).then(data => {
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(data)
}, 1000)
}))
}, 1000)
})
})
promise2.then(data => {
console.log(data)
})
// 200
3,测试手写的 Promise
输出结果:
Promise {
state: 'PENDING',
value: undefined,
reason: undefined,
onResolvedCallbacks: [],
onRejectedCallbacks: []
}
4,问题分析
let p = new Promise((resolve, reject) => {
// 1,执行器函数被立即执行
resolve(200)
}).then(data => {
// 2,进入成功回调处理,返回Promise
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(data)
}, 1000)
}))
}, 1000)
})
})
p.then(data => {
console.log(data)
})
分析代码的执行流程:
- new Promise 创建实例,executor 执行器函数被立即执行,
resolve(200)
被执行; - Promise 源码处理:在 resolve 方法中,存储了 value 值为 200、设置了 promise 状态为成功态、执行了成功回调(未收集的空数组);
- 调用
then
方法; - Promise 源码处理:then 方法内部创建 promise2-1,此时,p 为 DULFILLED 态,通过 setTimeout 创建宏任务1(获取 onFulfilled 返回值 x,调用resolvePromise 统一解析处理返回值 x),延迟到一下事件循环中处理;
- 执行
p.then
; - Promise 源码处理:then 方法内部创建新的 promise2-2,由于上个 promise2-1 创建的宏任务 1 尚未执行,所以,上个 promise2-1 的状态仍为 PENDING 态,因此,会对
p.then
中的成功/失败回调函数进行收集; - 至此,代码执行完毕,也就是第一轮宏任务执行完毕了;
- 宏任务 1 被执行,进入代码中第一个 then 的成功处理,data 为 200,返回值是一个 P
promise; - Promise 源码处理:内部拿到 then 的返回值 x,调用 resolvePromise 进行统一的解析处理,由于此处 x 是 promise,所以,在 resolvePromise 中会调用它的 then 方法;
- 但是,由于这个 promise(也就是 x),内部存在异步操作(setTimeout 1秒),所以当调用这个 promise(也就是 x)的 then 方法时,这个 promise(也就是 x)的状态仍为 PENDING 态
- 一秒后,执行 resolve(new Promise),此时,resolve 中的 promise 就是源码中的 y;
问题原因
- 手写的源码只处理了 then 中方法的返回值 x;但 resolve 中为 promise 的情况没有处理;(resolve 中的 promise 就是源码中的 y)
- 由于 y 是一个 promise,所以直接 resolve(y) ,即 then 中的 data,得到的就是 一个 PENDING 态的 promise 实例;
解决方案
- 由于 y 有可能是一个 promise 对象,所以对 y 使用 resolvePromise 进行递归处理,直到 y 为普通值为止;
相关 Promise A+规范内容:
5,代码实现
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('发生错误'))
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then;
if(typeof then === 'function'){
then.call(x, y => {
// resolve(y)
resolvePromise(promise2, y, resolve, reject) // 递归处理 y
}, r => {
reject(r)
});
}else{
resolve(x)
}
} catch (e) {
reject(e);
}
} else {
resolve(x)
}
}
三,Promise 的兼容处理
在开发中,所使用的 Promise 不一定都是原生的或我们自己实现的;
任何框架/库/开发者都有可能根据 promise A+ 规范实现一个 Promise;
为了确保使用这些 Promise 不会导致项目问题,需要对 Promise 进行兼容处理;
-
备注
理论上,严格按照 Promise A+ 规范实现的 Promise 是无需进行兼容处理的;所以,兼容处理主要用于弥补实现上的 Promise 漏洞和不严谨; -
比如:
let Promise1; // 自己实现的 Promise
let promise2;// 别人实现的 Promise
promise = new Promise1((resolve, reject) => {
resolve(1);
}).then(() => {
return new Promise2();
})
Promise 的兼容处理,有以下几处:
- 每个 Promise 实例的状态只能被改变一次;
四,promise-aplus-tests 测试
promise-aplus-tests 用于测试自己实现的 Promise 是否符合 Promise A+ 规范;
1,安装 promise-aplus-tests
npm install promises-aplus-tests -g
2,添加测试入口-创建延迟对象
- 创建延迟对象
Promise.deferred
延迟对象:一个具有“延时”效果的对象(因为对象中含有 Promise);
通过Promise.deferred
方法,测试当前 dfd 对象上的 Promise 实现是否符合 Promise A+ 规范要求;
Promise.deferred = function(){
let dfd = {}
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
这里使用了延迟对象,可以减少一层嵌套;
- 在
Promise.deferred
方法中,创建了一个 promise 对象dfd.promise
; - 相当于将 resolve/reject 绑定到
dfd.resolve
/dfd.reject
; - promise 成功就会调用 deferred 内部的
dfd.resolve
;失败调用dfd.reject
;
作用:将 promise 方法迁移到 dfd 对象上,通过对象直接访问能够少一层嵌套;
通过示例看一下延迟对象的效果:
- 不使用延迟函数:需嵌套一层
new Promise
function test() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(200)
}, 1000);
})
}
- 使用延迟函数:无需嵌套
new Promise
function test() {
let dfd = Promise.deferred();
setTimeout(() => {
dfd.resolve(200);
}, 1000);
return dfd.promise;
}
结论:
- 对比两种方式代码,使用延迟函数明显少了一层 new Promise 的嵌套;
- 每次调用
Promise.deferred()
就能够立即得到一个全新的 promise 实例; - 延迟对象,就是对象 deferred 中包含了 Promise 的“延迟”处理;
3,执行测试
promises-aplus-tests xxx(promise入口文件路径)
通过了 Promise A+ 规范 872 个测试用例;
五,结尾
本篇,主要对 Promise 源码进行完善并通过 promise-aplus-tests 测试,主要涉及以下几个点:
- 完善 Promise 源码:支持两种嵌套 promise 的情况;
- 分析 Promise 的执行过程;
- 创建延迟对象并通过 promise-aplus-tests 测试;
下篇,继续实现 Promise.resolve 和 Promise.reject;
维护日志
- 20211102
- 添加了“延迟函数部分”的说明和示例;
- 添加了对两种“嵌套返回 promise”的支持;
- 优化了Promise 执行过程分析的描述;
- 重新调整了文章一二级目录、结尾、摘要;
- 20211115
- 修改错别字;