实现 Promise.all
基础知识
Promise.all
是 Promise
构造函数上的一个静态方法,它接收一个可迭代对象(通常是 Promise 数组)作为参数,并返回一个新的 Promise
。这个方法是并行处理多个异步操作的强大工具。
要实现 Promise.all
,我们必须理解其核心的行为特性:
并行执行:
Promise.all
会等待所有传入的 Promise 都达到最终状态(fulfilled
或rejected
)。它允许所有异步操作并行开始,而不是一个接一个地等待。“全成则成” (All Success):
- 只有当所有传入的 Promise 都成功(
fulfilled
)时,由Promise.all
返回的新 Promise 才会成功。 - 成功时,其
resolve
的值是一个数组,包含了所有传入 Promise 的成功值,并且顺序与传入的 Promise 数组的顺序严格一致。
- 只有当所有传入的 Promise 都成功(
“一败俱败” (One Failure):
- 只要传入的 Promise 中有任何一个失败(
rejected
),由Promise.all
返回的新 Promise 就会立即失败。 - 失败时,其
reject
的原因是第一个失败的 Promise 的原因。
- 只要传入的 Promise 中有任何一个失败(
处理非 Promise 值: 如果传入的数组中包含非 Promise 的值(如数字、字符串、普通对象),
Promise.all
会将它们视为已经成功(fulfilled)的 Promise,并将其值直接放入结果数组中。
核心思路
我们的目标是创建一个函数 myPromiseAll(promises)
,它能模拟原生 Promise.all
的行为。核心思路可以概括为“计数器与结果收集”:
返回新 Promise: 函数必须返回一个新的
Promise
,因为Promise.all
本身就是一个异步过程。初始化:
- 创建一个数组
results
,用于按顺序存放所有成功的结果。 - 创建一个计数器
resolvedCount
,用于记录已经成功的 Promise 数量。
- 创建一个数组
处理空数组: 这是一个重要的边界情况。如果传入的数组为空,
Promise.all
应立即以一个空数组成功返回。遍历输入: 遍历传入的
promises
数组,对每个元素进行处理。使用索引i
来确保结果能被放置在results
数组的正确位置。统一处理: 对数组中的每个元素(无论是 Promise 还是普通值),都使用
Promise.resolve()
进行包装。这可以确保我们后续处理的是一个统一的 Promise 对象,极大地简化了逻辑。监听状态: 对每个包装后的 Promise 调用
.then()
方法,来监听它的成功与失败。- 成功时: 将成功的值存入
results
数组的对应索引位置,然后将resolvedCount
加一。每次增加后,都检查resolvedCount
是否等于总数。如果相等,说明所有 Promise 都已成功,此时resolve
主 Promise 并传入results
数组。 - 失败时: 一旦监听到任何一个 Promise 失败,无需等待其他 Promise,立即
reject
主 Promise,并将失败原因传递出去。
- 成功时: 将成功的值存入
代码实现
下面是一个遵循核心思路的 myPromiseAll
函数实现。
/**
* 自定义实现 Promise.all
* @param {Iterable<any>} promises - 一个可迭代对象,如 Promise 数组
* @returns {Promise<Array<any>>} - 返回一个新的 Promise
*/
function myPromiseAll(promises) {
// 1. 返回一个新的 Promise
return new Promise((resolve, reject) => {
// 2. 确保输入是数组(简化处理,原生支持所有可迭代对象)
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array.'));
}
const promiseCount = promises.length;
const results = new Array(promiseCount);
let resolvedCount = 0;
// 3. 处理传入数组为空的边界情况
if (promiseCount === 0) {
return resolve([]);
}
// 4. 遍历所有传入的 promise
promises.forEach((item, index) => {
// 5. 使用 Promise.resolve() 统一处理,兼容非 Promise 值
Promise.resolve(item).then(
// 6. 成功回调
(value) => {
results[index] = value; // 按顺序存放结果
resolvedCount += 1; // 计数器加一
// 如果所有 promise 都已成功,则 resolve 主 promise
if (resolvedCount === promiseCount) {
resolve(results);
}
},
// 7. 失败回调
(reason) => {
// 一旦有任何一个 promise 失败,立即 reject 主 promise
reject(reason);
}
);
});
});
}
代码解析
- 返回 Promise:函数主体为
return new Promise(...)
,遵循所有返回 Promise 函数的标准结构,通过 Promise 封装异步任务的最终状态。 - 初始化:
results
数组用于按顺序存储每个任务的结果。resolvedCount
计数器跟踪已完成的成功任务数。promiseCount
记录总任务数,通过new Array(promiseCount)
初始化固定长度数组,确保可直接通过索引赋值结果。
- 空数组处理:当
promiseCount === 0
时,立即返回Promise.resolve([])
,与原生Promise.all
对空数组的处理一致。 - 遍历任务数组:使用
forEach
遍历输入数组,通过index
参数保证结果数组顺序与输入一致,即使任务完成顺序不同。 - 统一转换为 Promise:通过
Promise.resolve(item)
处理每个任务:- 若
item
是 Promise,直接返回该 Promise。 - 若
item
是普通值,返回Promise.resolve(item)
包装的立即成功 Promise,确保后续处理的均为标准 Promise 对象。
- 若
- 成功回调处理:
- 子任务成功时,通过
results[index] = value
将结果存入对应索引位置。 resolvedCount++
递增计数器,当resolvedCount === promiseCount
时,说明所有任务成功,调用主 Promise 的resolve(results)
返回完整结果数组。
- 子任务成功时,通过
- 失败回调处理:任意子任务失败时,立即调用主 Promise 的
reject(reason)
,终止后续处理,遵循Promise.all
“一败俱败”的原则,后续任务结果被忽略。