Skip to content
实现 Promise.allSettled
概述
Promise.allSettled 是 ES2020 引入的静态方法,用来等待一组 Promise 全部“敲定”(settled)并返回结果数组。与 Promise.all 的区别在于,它不会在遇到第一个拒绝时立即停止,而是等所有 Promise 结束后,给出每个 Promise 的完成状态。
基本概念
- settled 状态:Promise 要么是“兑现”(fulfilled)、要么是“拒绝”(rejected),一旦到达其中任一状态,就称其已“敲定”(settled)。
allSettled关心的就是所有 Promise 是否都已敲定,而不在乎具体是成功还是失败。 - 结果对象:每个敲定的 Promise 会产生一个对象,
status为'fulfilled'时带有value字段,为'rejected'时带有reason字段。
工作原理
核心思路是 计数 + 按索引写入。
- 预分配一个长度等于函数列表长度的数组
result。 - 遍历函数列表,依次调用每个函数。对于每个返回的 Promise:
- 通过
.then将{ status: 'fulfilled', value }写入对应的索引位置。 - 通过
.catch将{ status: 'rejected', reason }写入。
- 通过
- 无论成功还是失败,都会进入
.finally,让计数器endCount自增。当endCount等于总长度时,说明所有 Promise 都已敲定,此时把result作为最终值 resolve 出去。
.finally 在这里充当了统一的记账点,避免了在 .then 和 .catch 中各自维护计数的分散写法。
基本用法与 API
原生 Promise.allSettled 的签名(简化后):
typescript
Promise.allSettled<T>(iterable: Iterable<T | PromiseLike<T>>): Promise<PromiseSettledResult<T>[]>它接收一个可迭代对象,每一项可以是普通值或 Promise;返回的 Promise 的结果数组中每一项的类型为:
typescript
interface PromiseFulfilledResult<T> {
status: "fulfilled";
value: T;
}
interface PromiseRejectedResult {
status: "rejected";
reason: any;
}
type PromiseSettledResult<T> = PromiseFulfilledResult<T> | PromiseRejectedResult;以下手工实现的 promiseAllSettled 接受一个函数数组(每个函数需返回 Promise),并返回 Promise<Obj[]>,其中 Obj 的类型为 { status: 'fulfilled'; value: string } | { status: 'rejected'; reason: string }。实现中直接处理了空数组的情况,行为与原生一致。
typescript
type FulfilledObj = {
status: 'fulfilled';
value: string;
};
type RejectedObj = {
status: 'rejected';
reason: string;
};
type Obj = FulfilledObj | RejectedObj;
function promiseAllSettled(functions: (() => Promise<string>)[]): Promise<Obj[]> {
return new Promise((resolve) => {
const result: Obj[] = new Array(functions.length);
let endCount = 0;
if (functions.length === 0) {
resolve(result);
return;
}
for (let i = 0; i < functions.length; i++) {
functions[i]()
.then((value) => {
result[i] = { status: 'fulfilled', value };
})
.catch((reason) => {
result[i] = { status: 'rejected', reason };
})
.finally(() => {
endCount += 1;
if (endCount === functions.length) {
resolve(result);
}
});
}
});
}注意:原 LeetCode 模板中
resolve参数存在拼写问题,这里已修正为规范的resolve。
示例
混合成功与失败
javascript
const f1 = () => Promise.resolve('成功');
const f2 = () => Promise.reject('失败原因');
const f3 = () => new Promise(resolve => setTimeout(() => resolve('延迟成功'), 100));
promiseAllSettled([f1, f2, f3]).then(console.log);
// 输出(顺序始终与传入函数一致):
// [
// { status: 'fulfilled', value: '成功' },
// { status: 'rejected', reason: '失败原因' },
// { status: 'fulfilled', value: '延迟成功' }
// ]空数组
传入空数组时,由于实现中的长度判断,会立即 resolve 一个空数组,与原生 Promise.allSettled 的行为一致:
javascript
promiseAllSettled([]).then(console.log); // []顺序保证
即使各 Promise 实际完成的先后顺序不同,因为写入时使用的是固定的索引 i,最终数组中的元素顺序仍与传入函数的顺序一一对应,不会发生错位。
注意点
- 计数器位置:使用
.finally统一计数比分别在.then和.catch中计数更简洁,且保证无论成功或失败都会执行。但需要环境支持.finally(ES2018 引入)。 - 顺序不变:结果数组的顺序只与函数在数组中的位置有关,与各 Promise 完成的先后无关。
- 与
Promise.all的差异:Promise.all在遇到第一个拒绝时立即 reject,其余 Promise 的结果会被丢弃;而allSettled会等待所有 Promise 结束,保留每一个的状态。 - 错误吞没:所有拒绝都会被
.catch捕获并包装为结果对象,不会向外抛出错误,这与原生行为一致。
限制
- 输入校验:该实现假定
functions中的每一项都是函数,且返回 Promise。如果某一项不是函数,functions[i]()会直接抛出TypeError,且无法被内部 Promise 捕获。而原生Promise.allSettled会通过Promise.resolve包装任意值。 - 同步异常:如果某个函数在调用时同步抛出异常(而非返回一个 rejected Promise),该异常不会被
.catch捕获,而是直接向上抛出,违背了allSettled的设计契约。实际的运行时环境通常需要使用Promise.resolve().then()或try…catch包裹以兼容同步错误。 - 环境依赖:
.finally在较老的引擎中不可用,回退方式是在.then的两个回调中分别计数,或者使用兼容写法。 - 并发数量:该实现会立即并发执行所有传入的函数,没有任何并发数控制。在函数个数极大时可能会创建大量微任务,不过这部分与原生
Promise.allSettled的并发行为一致。
