温故知新
上一篇《如何从无到有实现Promise(上)》中我们已经实现了一个看似可以正常工作的简易版 Promise ,不要认为这样就结束了,其实好戏才刚刚开始。
本篇我们继续改造和丰富这个 Promise,让它可以适用更复杂的场景。
本文篇幅较长,又有大量的代码片段,可以边记录笔记边阅读,对照着看更容以理解。
链式调用“有点东西”
众所周知 Promise 的 then 方法是支持链式调用的,如下所示:
- 链式调用场景1
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("first then");
}, 2000);
});
promise
.then((data) => {
console.log(data);
return "second then";
})
.then((data) => {
console.log(data);
});
输出结果应该为,先打印 first then ,两秒后打印 second then。
但是我们目前实现的 Promise 是不支持的,那该如何实现 Promise 实例的 then 方法支持链式调用这个行为呢?
在 Promise 实例的 then 方法内的 onfulfilled 、onrejected 函数中,是支持再次返回一个 Promise 实例的,也支持返回一个普通值(非 Promise 实例);并且返回的这个 Promise 实例或者这个普通值将会传给下一个 then 方法里 onfulfilled 、onrejected 函数中,如此, then 方法就支持了链式调用。
如上总结,想要支持 then 的链式调用,就要每一个 then 方法的 onfulfilled 函数和 onrejected 函数都要返回一个 Promise 实例。
我们先支持链式调用 then 方法时返回一个普通的值的情况,改造 then 方法:
Promise.prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype) {
let promise2;
if (this.status === "fulfilled") {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onfulfilled(this.resolveVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
if (this.status === "rejected") {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.rejectVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
if (this.status === "pending") {
return (promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(() => {
try {
let result = onfulfilled(this.resolveVal);
resolve(result);
} catch (e) {
reject(e);
}
});
this.onRejectedArray.push(() => {
try {
let result = onrejected(this.rejectVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
};
结合我们上面的分析,代码不难理解,当调用 Promise 实例 then 方法的时候,应该再次返回一个 Promise 实例,promise2 就将作为 then 方法的返回值。
这个 promise2 是什么时候被 resolve 或者 reject 的呢?
- 当判断分支是
status === "fulfilled"
或者status === "rejected"
的时候!
这个 promise2 在执行完 then 方法时就被 resolve 或者 reject 了。
- 当判断分支为
status === "pending"
时情况较为复杂!
返回的 promise2 实例的 resolve 和 reject 是放到 onFulfilledArray 和 onRejectedArray数组中的。
当异步执行完成后,依次执行 onFulfilledArray 和 onRejectedArray 数组内的函数时才执行了 promise2 的 resolve、reject。
那么在 onFulfilledArray 和 onRejectedArray 数组中的函数内应该 resolve 或是 reject 掉 promise2,并且传入的参数就是 onfulfilled 或者 onrejected 的执行结果。
这样 then 方法支持链式调用,并且支持返回一个普通值的情况。
再来整体看下目前为止的完整代码:
function Promise(excutor) {
this.status = "pending";
this.resolveVal = null;
this.rejectVal = null;
this.onFulfilledFuncArray = [];
this.onRejectedFuncArray = [];
const resolve = (value) => {
setTimeout(() => {
if (this.status === "pending") {
this.resolveVal = value;
this.status = "fulfilled";
this.onFulfilledFuncArray.forEach((fn) => {
fn(this.resolveVal);
});
}
}, 0);
};
const reject = (error) => {
setTimeout(() => {
if (this.status === "pending") {
this.rejectVal = error;
this.status = "rejected";
this.onRejectedFuncArray.forEach((fn) => {
fn(this.rejectVal);
});
}
}, 0);
};
try {
excutor(resolve, reject);
} catch (error) {
reject(error);
}
}
Promise.prototype.then = function (
onfulfilled = Function.prototype,
onrejected = Function.prototype
) {
let promise2;
if (this.status === "fulfilled") {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onfulfilled(this.resolveVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
if (this.status === "rejected") {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.rejectVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
if (this.status === "pending") {
return (promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(() => {
try {
let result = onfulfilled(this.resolveVal);
resolve(result);
} catch (e) {
reject(e);
}
});
this.onRejectedArray.push(() => {
try {
let result = onrejected(this.rejectVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
};
上面也说了,then 方法也是支持显式返回一个 Promise 实例的情况,如下。
- 链式调用场景2
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("first then");
}, 2000);
});
promise
.then((data) => {
console.log(data);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("second then");
}, 2000);
});
})
.then((data) => {
console.log(data);
});
第一种情况是 then 方法的 onFulfilled 和 onrejected 函数返回的是一个普通值,与之不同的是这里我们要支持 onFulfilled 和 onrejected 函数返回的是一个 Promise 实例。
let result = onfulfilled(this.resolveVal);
let result = onrejected(this.rejectVal);
场景1中,上面两行代码得到的结果都是一个普通值,也就是说 result 就是一个普通值,现在要做的是让 result 可以为普通值,也可以为 Promise 实例。所以我们就不能直接对 result 进行 resolve(result) 的操作。
综上所述,我们抽象出 resolvePromise 方法进行统一处理,替换原 resolve 方法。
接下来就要完成这个 resolvePromise 函数:
首先我们定义方法参数:
- promise2:返回的 Promise 实例
- result:onfulfilled 或者 onrejected 函数的返回值
- resolve: promise2 的 resolve 方法
- reject: promise2 的 reject 方法
方法实现为:
const resolvePromise = (promise2, result, resolve, reject) => {
if ((typeof result === "function" || typeof result === "object") && result !== null) {
try {
if (typeof result.then === "function") {
result.then.call(
result,
function (value) {
return resolvePromise(promise2, value, resolve, reject);
},
function (error) {
return reject(error);
}
);
} else {
resolve(result);
}
} catch (e) {
return reject(e);
}
} else {
resolve(result);
}
};
看不懂不要急,此处是本文最大的难点了,也是链式调用的核心所在,接下来将目前的代码整合在一起,然后仔细分析下 resolvePromise 究竟做了什么。
附加注释的完整代码如下:
function Promise(executor) {
this.status = "pending";
this.resolveVal = null;
this.rejectVal = null;
this.onFulfilledArray = [];
this.onRejectedArray = [];
const resolve = (value) => {
setTimeout(() => {
if (this.status === "pending") {
this.resolveVal = value;
this.status = "fulfilled";
this.onFulfilledArray.forEach((fn) => {
fn(this.resolveVal);
});
}
});
};
const reject = (error) => {
setTimeout(() => {
if (this.status === "pending") {
this.rejectVal = error;
this.status = "rejected";
this.onRejectedArray.forEach((fn) => {
fn(this.rejectVal);
});
}
});
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
// 参数1:promise2实例 参数2:onfulfilled 和 onrejected 执行结果 参数3、4:promise2 的 resolve 以及 reject
const resolvePromise = (promise2, result, resolve, reject) => {
// 如果返回数据可能是 Promise 类型(再进行 .then 判断后才可真正确认,在下面处理)
if (
(typeof result === "function" || typeof result === "object") &&
result !== null
) {
try {
// 通过 then 方法判断可以确定是否是 Promise 类型
if (typeof result.then === "function") {
// 执行 then 方法 , 参数分别为 onfulfilled 和 onrejected
result.then.call(
result,
function (value) {
// onfulfilled 函数
// 当 result 是 Promise 类型时,递归调用 resolvePromise 函数,直到 result 不再是 Promise 类型,执行 promise2 (当前then的返回promise)的 resolve。
return resolvePromise(promise2, value, resolve, reject);
},
function (error) {
// onrejected 函数
return reject(error);
}
);
} else {
// result 不是Promise类型,直接 resolve promise2
resolve(result);
}
} catch (e) {
return reject(e);
}
} else {
// result 不是Promise类型,直接 resolve promise2
resolve(result);
}
};
Promise.prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype) {
// Promise 需要支持链式调用,所以 then 方法也要返回一个 Promise 实例
let promise2;
// 因为 resolve 函数内逻辑是异步执行的,所以只有 then 方法被异步调用,才会进入这个分支,在执行 then 方法的时候,resolve 操作已经完成,状态已经变更
if (this.status === "fulfilled") {
return (promise2 = new Promise((resolve, reject) => {
// 返回的 promise2 中的代码要异步执行
setTimeout(() => {
try {
// 执行 onfulfilled 函数,得到返回结果。
let result = onfulfilled(this.resolveVal);
// 得到结果 result 可能是普通值,可能依然是 Promise 实例,通过 resolvePromise 进行处理
// 并且在 resolvePromise 函数中进行 promise2 的 resolve 或者 reject 操作
resolvePromise(promise2, result, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
// 与上面同理
if (this.status === "rejected") {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.rejectVal);
resolvePromise(promise2, result, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
// 因为 resolve 函数内逻辑是异步执行的,因此当 then 方法被同步调用的时候,resolve 内的逻辑还未执行,状态依然是 pending
// 此时需要保存调用 then 方法时传入的 onfulfilled 和 onrejected 函数,在 resolve 执行时再取出执行
if (this.status === "pending") {
return (promise2 = new Promise((resolve, reject) => {
// 因为同一个 Promise 实例可能有多个 then 方法,所以将所有 then 方法内的 onfulfilled 函数进行保存,需要时依次执行
this.onFulfilledArray.push((value) => {
try {
// 执行 onfulfilled 函数,得到返回结果。
let result = onfulfilled(value);
// 得到结果 result 可能是普通值,可能依然是 Promise 实例,通过 resolvePromise 进行处理
// 并且在 resolvePromise 函数中进行 promise2 的 resolve 或者 reject 操作
resolvePromise(promise2, result, resolve, reject);
} catch (e) {
reject(e);
}
});
this.onRejectedArray.push((error) => {
try {
let result = onrejected(error);
resolvePromise(promise2, result, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
};
这应该是两篇文章到现在最接近最终实现并且注释最详细的一次了。不过依然不能改变它非常难以理解的事实,理解的关键在于弄清楚当 result 为 Promise 类型时,和 promise2 的关系,以及当 result 为 Promise 类型时递归调用 resolvePromise 函数的目的。
没有什么捷径,只能多看多写多思考吧,写出测试代码,然后顺着执行顺序去跟踪代码。
我想出了这个流程图尽可能地帮助大家理解。
先看测试代码:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("first then");
}, 2000);
});
promise
.then((data) => {
console.log(data);
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(`second then`);
}, 2000);
});
})
.then((data) => {
console.log(data);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`last then`);
}, 2000);
});
})
.then((data) => {
console.log(data);
});
2秒后输出 'first then' 再过2秒后输出 'second then' 再过2秒后输出 'last then'
回过头来再看,其实 resolvePromise 方法的作用非常明确,当 onfulfilled 函数返回的数据(result)为普通值的话,还是像场景1一样直接 resolve promise2 处理即可,但是如果当这个 result 为 Promise 类型时,就要在 result.then.onfulfilled 中去递归调用 resolvePromise ,当再进去 resolvePromise 的时候,此时新的 result 参数如果是普通值了,就 resolve promise2 ,并将结果作为参数返回即可。
到这如果你都可以理解,可以说是基本掌握了 Promise 的除静态方法外的全部基础内容。为了简化代码,更容易理解,一些容错机制没有添加,并不影响整体思路的学习。
静态方法,不说你也会
静态方法其实非常简单,不说你也应该会,但是我还是简单说下吧[捂脸]。
关于 Promise 的静态方法如 Promise.resolve、 Promise.reject、 Promise.all 等等,就不再一一实现了,只选择平时用的比较多的 Promise.all 实现以下。
- Promise.all 的实现
Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);
如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。
看下具体使用。
场景1:
const promise1 = new Promise((resolve, reject) => {
resolve("p1");
});
const promise2 = new Promise((resolve, reject) => {
resolve("p2");
});
Promise.all([promise1, promise2]).then((data) => {
console.log(data);
});
打印出 ["p1", "p2"]
场景2:
const promise1 = new Promise((resolve, reject) => {
resolve("p1");
});
const promise2 = new Promise((resolve, reject) => {
reject("p2 失败");
});
Promise.all([promise1, promise2])
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
打印出 p2 失败
对照着使用,来实现这个 Promise.all :
Promise.all = function (promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new TypeError("promiseArray should be array!");
}
// 在外层包裹一个 Promise ,如果内部有一个 Promise 执行不成功,就执行最外层 Promise 的 reject
return new Promise((resolve, reject) => {
try {
let resultArray = [];
for (let i = 0; i < promiseArray.length; i++) {
promiseArray[i].then((data) => {
// 记录 resolve 的值
resultArray.push(data);
// 全部完成后 执行外出 Promise 的 resolve 将存放每一个成功值得数组返回即可
if (resultArray.length === promiseArray.length) {
resolve(resultArray);
}
}, reject);
}
} catch (e) {
reject(e);
}
});
};
实现起来非常容易,其他几个静态方法思路也大同小异,有时间可以自行补充。
结束啦!
总结 Promise 的两篇笔记终于结束啦,实现 Promise 并不是目的,况且也不是完全按照规范去实现的,目的是学习思路,深层次得理解原理,达到融会贯通,这样以后不论是自己的代码设计参考它的实现原理还是在 Promise 的使用上遇到问题,解决起来都会得心应手。
这部分内容还是比较难理解的,整理笔记得时候也会有很多地方需要静下心来梳理思路。刚接触一头雾水也再正常不过了,不要灰心,多看多写多想,每看一次都会有不同的收获。