Promise.all到底在做什么,根据MDN的描述,试着实现一下
/**
* Promise工具函数_withResolvers, 返回一个对象:
* 其中包含一个Promise实例: promise,以及用来改变这个实例promise状态的 resolve 和 reject 函数
* 由于node环境不支持Promise.withResolvers(),所以我们选择手动实现
* @returns {promise, resolve, reject}
*/
Promise._withResolvers = function _withResolvers() {
let resolve;
let reject;
let promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
return {promise, resolve, reject};
}
/**
* 判断入参是否为PromiseLike: 是ES官方定义实现的Promise实例 or 符合Promise A+规范 --带有一个then方法的 promise
* @param {*} target
* @returns
*/
function isPromiseLike(target) {
return target && typeof target === 'function';
}
/**
* Promise.all 的显示实现
* @param {*} tasks tasks is an iterable object: Array、Set...
*/
Promise._all = function _all(tasks) {
// 在node环境下需要自己实现 withResolvers
// promise是_all方法的返回值,
const {promise, resolve, reject} = Promise._withResolvers();
let total = 0; // 一共有多少个待执行的任务
let finished = 0; // 已完成的任务数量
let result = []; // 返回值,当所有的任务正确完成后,按任务的加入顺序进行放置每一个Promise
// 因为Promise.all的参数要求是一个可迭代对象,所以可以用 for of 进行遍历
for(const task of tasks) {
let index = total++; // 总任务数量+1, 并记录每个任务的索引
/**
* 注意每一次进行迭代时,我们都单独声明一个处理函数_resolve,用于操作result
* .then()中_resolve函数的操作是异步执行的,调用_resolve 时,total 已经能代表总的任务数量了
* 但由于 _resolve 创建时,可以访问外部作用域,所以,index就代表了从 0 开始的结果下标
* 我们不知道result[index]何时被赋值,也不知道哪一个index是最后设置的
*/
const _resolve = res => {
// 当任务正确结束的时候,根据传入的顺序放置结果,并且记录已经完成的总数量
result[index] = res;
finished++;
if(finished === total) { // _resolve 方法执行时,这里的total已经能代表tasks的总数量了
resolve(result);
}
}
/**
* 每一轮迭代,Promise.resolve(task)会创建一个新的Promise
* 如果这个Promise被完成,则调用_resolve函数,将其加入结果集
* 如果这个Promise被拒绝,则会调用reject
*/
Promise.resolve(task).then(_resolve, reject);
}
// 如果任务列表为空,直接将promise的状态设置为fulfilled
if(total === 0) {
resolve(result);
}
return promise;
}
我们将官网的用例拿来测试一下:
const p1 = Promise.resolve(3);
const p2 = 1337;
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("foo");
}, 100);
});
Promise._all([p1, p2, p3]).then((values) => {
console.log(values); // [3, 1337, "foo"]
});
ok,完成。