Promise.all是javascript中处理并发请求的核心方法。面试中经常会被要求手写实现,以考察对Promise状态机、异步处理以及数组遍历的掌握。
1. Promise.all的核心逻辑
在动手写之前,先明确它的四个特性:
-
输入:接收一个可迭代对象(通常是数组)
-
返回:返回一个新的Promise
-
成功条件:只有当数组中所有的Promise都成功时,新Promise才成功,并返回一个按顺序排列的结果数组
-
失败条件: 只要有一个Promise失败,新Promise立即失败(Fail-fase),并返回第一个失败的错误
2. 手写代码实现
/**
* 实现Promise.all
*
* @param promises 一个包含多个Promise实例的数组
* @returns 一个新的Promise实例,只有当所有传入的Promise实例都成功时才会成功,否则会在第一个失败的Promise实例时失败
*/
function promissAll<T>(promises: Promise<T>[]): Promise<T[]> {
// 1. 返回一个新的Promise实例
return new Promise<T[]>((resolve, reject) => {
// 判断参数是否为可迭代对象
if (!promises || typeof promises[Symbol.iterator] !== 'function') {
return reject(new TypeError('Argument is not iterable'));
}
const results: T[] = []; // 用于存储每个Promise的结果
let completedCount = 0; // 用于跟踪已完成的Promise数量
const promiseArray = Array.from(promises); // 将传入的参数转换为数组
const total = promiseArray.length; // 获取Promise的总数量
// 2. 如果传入的数组为空,立即resolve一个空数组
if (total === 0) {
resolve(results);
return;
}
// 3. 遍历每个Promise实例
promiseArray.forEach((promise, index) => {
// 使用Promise.resolve确保即使传入的不是Promise实例也能正确处理
Promise.resolve(promise).then((value) => {
results[index] = value; // 存储当前Promise的结果
completedCount++;
// 如果所有Promise都已完成,resolve最终结果数组
if (completedCount === total) {
resolve(results);
}
})
}, (error) => {
// 4. 如果有任何一个Promise实例失败,立即reject
reject(error);
})
})
}
3. 实现细节解析
Q1: 为什么不直接result.push(value)?
- 原因: Promise是异步的,执行完成的时间不确定。如果使用push,返回的结果顺序会乱,必须通过result[index]确保结果和原数组的索引一一对应。 Q2: 为什么用count计数而不是判断result.length?
- 原因:在javascript数组中,如果你先复制了索引为2的值result[2] = 'a', 此时result.length 会直接变成3,但索引0和1可能还没完成。所以必须使用独立的计数器。
Q3: Promise.resolve(promise)的作用?
- 请在评论区回复
Q4: Fail-fase(快速失败)机制
- 一旦其中一个Promise触发了reject,新的Promise的状态就会变成rejected。由于Promise的状态只能改变一次,后续Promise成功的调用会被忽略
4. 测试用例
const p1 = Promise.resolve(1);
const p2 = new Promise((resolve) => setTimeout(() => resolve(2), 1000));
const p3 = 3; // 普通值
promissAll([p1, p2, p3])
.then(res => console.log('成功:', res)) // 1秒后输出: 成功: [1, 2, 3]
.catch(err => console.log('失败:', err));
const p4 = Promise.reject('报错了');
promissAll([p1, p4, p2])
.then(res => console.log(res))
.catch(err => console.log('失败:', err)); // 立即输出: 失败: 报错了
5. 总结
手写 Promise.all 的口诀:返回一个新 Promise,遍历数组看结果,索引对应存数据,全部成功才 resolve,一个失败就 reject。
细心的老铁肯定也看到了,Q3没有输出答案,欢迎各位大牛在评论区留言