一句话概括
Promise.all、Promise.race、Promise.allSettled、Promise.any 是 Promise 的四个核心静态方法,分别对应”全部成功”、”最快完成”、”全部落定”、”任一成功”四种并发控制语义,掌握其原理是异步编程进阶的必经之路。
背景
在实际业务中,我们经常需要同时发起多个异步请求,并根据这些请求的结果做出不同的处理:
- 页面初始化时同时请求用户信息、权限列表、配置数据,全部成功才渲染
- 多个 CDN 节点同时请求资源,哪个快用哪个
- 批量操作后无论成功失败都要汇总结果
- 多个备用接口中任意一个成功即可
这四种场景分别对应 Promise.all、Promise.race、Promise.allSettled、Promise.any。
概念与定义
Promise.all
- 接收一个可迭代对象(通常是数组)
- 全部 resolve → 返回所有结果的数组(顺序与输入一致)
- 任意一个 reject → 立即 reject,返回第一个失败的原因
- 空数组 → 立即 resolve
[]
Promise.race
- 接收一个可迭代对象
- 第一个落定(无论 resolve 还是 reject) → 以该结果落定
- 空数组 → 永远 pending(永不落定)
Promise.allSettled
1
| Promise.allSettled(iterable)
|
- ES2020 引入
- 等待所有 Promise 落定(无论成功失败)
- 返回结果数组,每项格式:
{ status: 'fulfilled', value: ... }{ status: 'rejected', reason: ... }
- 永远不会 reject
Promise.any
- ES2021 引入
- 任意一个 resolve → 立即 resolve,返回第一个成功的值
- 全部 reject → reject,抛出
AggregateError(包含所有失败原因) - 空数组 → 立即 reject
AggregateError
最小示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.reject('error');
// Promise.all:全部成功
Promise.all([p1, p2]).then(console.log); // [1, 2]
// Promise.all:任一失败
Promise.all([p1, p3]).catch(console.log); // 'error'
// Promise.race:最快落定
Promise.race([
new Promise(r => setTimeout(() => r('slow'), 1000)),
new Promise(r => setTimeout(() => r('fast'), 100)),
]).then(console.log); // 'fast'
// Promise.allSettled:全部落定
Promise.allSettled([p1, p3]).then(console.log);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' }
// ]
// Promise.any:任一成功
Promise.any([p3, p1]).then(console.log); // 1
|
核心知识点拆解
1. Promise.all 的短路特性
Promise.all 一旦有一个 Promise reject,会立即 reject,不会等待其他 Promise 完成。但其他 Promise 仍然在执行,只是结果被忽略了。
1
2
3
4
5
6
| const p1 = new Promise((_, reject) => setTimeout(() => reject('p1 fail'), 100));
const p2 = new Promise(resolve => setTimeout(() => resolve('p2 ok'), 200));
Promise.all([p1, p2])
.catch(err => console.log('caught:', err)); // caught: p1 fail
// p2 仍然会在 200ms 后 resolve,但结果被忽略
|
2. Promise.all 结果顺序保证
结果数组的顺序与输入数组的顺序严格一致,与 Promise 的完成顺序无关:
1
2
3
4
| const p1 = new Promise(r => setTimeout(() => r('slow'), 200));
const p2 = new Promise(r => setTimeout(() => r('fast'), 50));
Promise.all([p1, p2]).then(console.log); // ['slow', 'fast'](顺序与输入一致)
|
3. Promise.race 与 Promise.any 的区别
| 方法 | 触发条件 | 失败处理 |
|---|
race | 第一个落定(成功或失败) | 第一个 reject 就 reject |
any | 第一个成功 | 全部失败才 reject |
1
2
3
4
5
| const fail = Promise.reject('fail');
const slow = new Promise(r => setTimeout(() => r('ok'), 100));
Promise.race([fail, slow]).catch(e => console.log('race:', e)); // race: fail
Promise.any([fail, slow]).then(v => console.log('any:', v)); // any: ok
|
4. Promise.allSettled 的使用场景
适合批量操作后汇总结果,不关心单个失败:
1
2
3
4
5
6
7
8
| const requests = [fetchUser(1), fetchUser(2), fetchUser(3)];
Promise.allSettled(requests).then(results => {
const succeeded = results.filter(r => r.status === 'fulfilled').map(r => r.value);
const failed = results.filter(r => r.status === 'rejected').map(r => r.reason);
console.log('成功:', succeeded);
console.log('失败:', failed);
});
|
5. 非 Promise 值的处理
所有静态方法都会将非 Promise 值通过 Promise.resolve() 包装:
1
| Promise.all([1, 2, Promise.resolve(3)]).then(console.log); // [1, 2, 3]
|
实战案例
案例一:页面初始化并发请求
1
2
3
4
5
6
7
8
9
10
11
12
| async function initPage() {
try {
const [userInfo, permissions, config] = await Promise.all([
fetchUserInfo(),
fetchPermissions(),
fetchConfig(),
]);
renderPage(userInfo, permissions, config);
} catch (err) {
showError('页面初始化失败:' + err.message);
}
}
|
案例二:CDN 容灾切换
1
2
3
4
5
6
7
8
9
10
| async function fetchWithFallback(urls) {
try {
// 任意一个成功即可
const result = await Promise.any(urls.map(url => fetch(url)));
return result;
} catch (e) {
// AggregateError:所有 CDN 都失败
throw new Error('所有 CDN 节点均不可用');
}
}
|
案例三:批量删除汇总结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| async function batchDelete(ids) {
const results = await Promise.allSettled(ids.map(id => deleteItem(id)));
const successIds = [];
const failedIds = [];
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
successIds.push(ids[index]);
} else {
failedIds.push({ id: ids[index], reason: result.reason });
}
});
return { successIds, failedIds };
}
|
案例四:超时控制
1
2
3
4
5
6
7
8
9
10
11
| function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`超时:${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}
// 使用
withTimeout(fetchData(), 3000)
.then(data => console.log(data))
.catch(err => console.log(err.message)); // 超时:3000ms
|
底层原理
Promise.all 实现原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| Promise.myAll = function(iterable) {
return new Promise((resolve, reject) => {
const promises = Array.from(iterable);
if (promises.length === 0) {
resolve([]);
return;
}
const results = new Array(promises.length);
let resolvedCount = 0;
promises.forEach((p, index) => {
// 用 Promise.resolve 包装,处理非 Promise 值
Promise.resolve(p).then(
value => {
results[index] = value; // 保证顺序
resolvedCount++;
if (resolvedCount === promises.length) {
resolve(results); // 全部完成才 resolve
}
},
reason => {
reject(reason); // 任一失败立即 reject
}
);
});
});
};
|
Promise.race 实现原理
1
2
3
4
5
6
7
8
9
| Promise.myRace = function(iterable) {
return new Promise((resolve, reject) => {
for (const p of iterable) {
// 第一个落定的 Promise 决定最终结果
// 后续的 resolve/reject 调用会被忽略(Promise 状态不可逆)
Promise.resolve(p).then(resolve, reject);
}
});
};
|
Promise.allSettled 实现原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| Promise.myAllSettled = function(iterable) {
return new Promise(resolve => {
const promises = Array.from(iterable);
if (promises.length === 0) {
resolve([]);
return;
}
const results = new Array(promises.length);
let settledCount = 0;
promises.forEach((p, index) => {
Promise.resolve(p).then(
value => {
results[index] = { status: 'fulfilled', value };
settledCount++;
if (settledCount === promises.length) resolve(results);
},
reason => {
results[index] = { status: 'rejected', reason };
settledCount++;
if (settledCount === promises.length) resolve(results);
}
);
});
});
};
|
Promise.any 实现原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| Promise.myAny = function(iterable) {
return new Promise((resolve, reject) => {
const promises = Array.from(iterable);
if (promises.length === 0) {
reject(new AggregateError([], 'All promises were rejected'));
return;
}
const errors = new Array(promises.length);
let rejectedCount = 0;
promises.forEach((p, index) => {
Promise.resolve(p).then(
value => {
resolve(value); // 任一成功立即 resolve
},
reason => {
errors[index] = reason;
rejectedCount++;
if (rejectedCount === promises.length) {
// 全部失败才 reject
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
});
});
};
|
高频面试题解析
Q1:Promise.all 和 Promise.allSettled 的区别?
答:
| 对比项 | Promise.all | Promise.allSettled |
|---|
| 引入版本 | ES2015 | ES2020 |
| 失败处理 | 任一失败立即 reject | 等待全部落定,不会 reject |
| 返回值 | 成功值数组 | {status, value/reason} 数组 |
| 适用场景 | 全部成功才有意义 | 需要汇总所有结果 |
Q2:Promise.race 和 Promise.any 的区别?
答:
Promise.race:第一个落定(无论成功失败)就决定结果。如果第一个是 reject,整体就 reject。Promise.any:第一个成功才决定结果。只有全部失败才 reject,并抛出 AggregateError。
Q3:Promise.all 传入空数组会怎样?
答: 立即 resolve 一个空数组 []。
1
| Promise.all([]).then(v => console.log(v)); // []
|
而 Promise.race([]) 会永远 pending,因为没有任何 Promise 可以落定。
Q4:Promise.all 中某个 Promise reject 后,其他 Promise 还会执行吗?
答: 会继续执行,但结果会被忽略。Promise.all 只是不再等待其他结果,但无法取消已经发出的异步操作(如网络请求)。如果需要取消,需要配合 AbortController。
Q5:如何实现一个带超时的 Promise.all?
1
2
3
4
5
6
| function allWithTimeout(promises, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
return Promise.race([Promise.all(promises), timeout]);
}
|
Q6:AggregateError 是什么?
答: ES2021 引入的错误类型,专门用于表示多个错误的集合。Promise.any 在全部 reject 时抛出,包含 errors 属性(所有失败原因的数组)。
1
2
3
4
5
| Promise.any([Promise.reject('a'), Promise.reject('b')])
.catch(e => {
console.log(e instanceof AggregateError); // true
console.log(e.errors); // ['a', 'b']
});
|
总结与扩展
四个方法对比总结
| 方法 | 成功条件 | 失败条件 | 返回值 | 引入版本 |
|---|
all | 全部 resolve | 任一 reject | 结果数组 | ES2015 |
race | 第一个落定 | 第一个落定 | 第一个结果 | ES2015 |
allSettled | 全部落定 | 永不失败 | 状态对象数组 | ES2020 |
any | 任一 resolve | 全部 reject | 第一个成功值 | ES2021 |
扩展阅读
Promise.all 的并发控制:Promise.all 会同时发起所有请求,如果需要限制并发数,需要手动实现调度器(下周主题)AbortController 配合使用:在 Promise.race 超时后,通过 AbortController 取消未完成的请求Promise.any 的 Polyfill:在不支持 ES2021 的环境中,可以用 Promise.all + 反转逻辑实现
记忆口诀
- all:全赢才赢,一输即输
- race:第一名说了算
- allSettled:等所有人跑完,不管名次
- any:有一个赢就赢,全输才输