手动实现 Promise.all
Posted: 08.25.2020
介绍
Promise 大家应该都懂,Promise.all 相信大家也都用过。本来我以为会用就够了,结果在刷面经的时候偶然发现了这么一个问题:
怎么手动实现 Promise.all ?
我想了一下,没有想到什么好的方法。后来专门去研究了一下。
仔细分析一下,我们要实现的功能,本质上就是一个大的 Promise。在这个 Promise 里,我们需要在所有的小 Promise 被 resolve 的时候, resolve 这个大的 Promise。并且在一个小的 Promise 被 reject 之后,reject 大的 Promise。
因此,问题的关键就在于,怎么确定什么时候该 resolve,什么时候该 reject。
从一个宏观的层面上来说,我们可以这么做:
- 设置一个变量 len 来记录当前有多少个 Promise 还没处理
- 当一个小的 Promise resolve/reject 之后,记录其的输出,并且 len--
- 当 len === 0 的时候,我们判断究竟要 resolve 还是 reject 整个大的 Promise
那么问题其实就被拆分成了很多小的部分,其中最大的问题是:我要怎么知道 len 变成 0 了?
这个时候就需要引入 ES6 一种新的 API,Proxy。
代码实现
Promise.myall = function () {
/**
* 读取参数
* 这里就不做校验了,感兴趣的可以自己做
*/
const promises = arguments[0];
return new Promise((resolve, reject) => {
// 如果最后所有的小 Promise resolve 了,ans 就是我们需要 resolve 的内容
const ans = new Array(promises.length);
/**
* len 代表了还剩多少 Promise 没被处理
* errFlag 代表了是否有 Promise 被 reject 了
*/
let len = {
len: promises.length,
errFlag: false
};
/**
* 创建一个 Proxy 来监控 len 的改变
* 每当 len 这个对象被 update 时,setter 会被触发
* 注意,proxy 必须在迭代 Promise 之前创建,因为接下来要用
*/
const proxy = new Proxy(len, {
set: function (target, key, value) {
// 进行 update
target[key] = value;
// 如果某个 Promise 被 reject 了,那么我们立刻 reject 大的 Promise
if (key === 'errFlag' && value === true) {
reject(errObject);
}
/**
* 如果所有 Promise 都已经 resolve/reject 了
* 我们就可以 resolve 最后的数组了
*/
if (value === 0) {
resolve(ans);
}
}
});
// 设置一个 errObject 来记录 reject 的内容
let errObject;
for (let i = 0; i < promises.length; ++i) {
promises[i]
.then(data => {
ans[i] = data;
// 这里通过 proxy 进行 update
proxy.len--;
})
.catch((err) => {
if (!errObject) errObject = err;
proxy.errFlag = true;
proxy.len--;
});
}
});
}
检查
我们将利用 async await 来检查我们上面的方法。
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 5000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 3000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 1000);
});
// record time
const hrstart = process.hrtime();
(async function () {
try {
const data = await Promise.myall([promise1, promise2, promise3]);
console.log('resolve!');
console.log(data);
console.log('================');
// measure time
const hrend = process.hrtime(hrstart);
console.info('所花时间: %ds %dms', hrend[0], hrend[1] / 1000000);
} catch (e) {
console.log('reject!');
console.log(e);
console.log('================');
// measure time
const hrend = process.hrtime(hrstart);
console.info('所花时间: %ds %dms', hrend[0], hrend[1] / 1000000);
}
}())
resolve:

reject:

完美!