为什么 Promise 组合方法很重要
单个 Promise 处理单个异步操作,但实际开发中经常需要组合多个异步操作:等待所有操作完成、等待第一个完成、处理部分失败等。
Promise.all、Promise.race、Promise.allSettled、Promise.any 这四个静态方法提供了不同的组合语义,理解它们是掌握 JavaScript 异步编程的关键。
一、Promise.all
1.1 基本用法
Promise.all 等待所有 Promise 完成,任何一个失败则整体失败:
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(values => console.log(values)); // [1, 2, 3]
// 任何一个失败
const pFail = Promise.reject(new Error('failed'));
Promise.all([p1, pFail, p3])
.catch(err => console.error(err.message)); // 'failed'
1.2 并发模型
Promise.all 并发执行所有 Promise,不会等待一个完成才启动下一个:
const start = Date.now();
Promise.all([
fetch('/api/1').then(() => console.log(1, Date.now() - start)),
fetch('/api/2').then(() => console.log(2, Date.now() - start)),
fetch('/api/3').then(() => console.log(3, Date.now() - start))
]);
// 三个请求几乎同时发出
1.3 空数组
Promise.all([]).then(values => console.log(values)); // []
// 空数组会立即 resolved
二、Promise.race
2.1 基本用法
Promise.race 返回最先完成(无论成功或失败)的 Promise 结果:
const p1 = new Promise(resolve => setTimeout(() => resolve(1), 100));
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 50));
const p3 = new Promise((_, reject) => setTimeout(() => reject(new Error('fail')), 30));
Promise.race([p1, p2, p3])
.then(value => console.log(value)) // 不执行
.catch(err => console.error(err.message)); // 'fail'
// p3 最先完成(失败)
2.2 应用场景
超时控制:
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
return Promise.race([promise, timeout]);
}
withTimeout(fetch('/api/slow'), 5000)
.then(data => console.log(data))
.catch(err => console.error(err.message)); // 5秒后 Timeout
三、Promise.allSettled
3.1 基本用法
ES2020 引入。Promise.allSettled 等待所有 Promise 结束,不关心成功或失败:
const p1 = Promise.resolve(1);
const p2 = Promise.reject(new Error('failed'));
const p3 = Promise.resolve(3);
Promise.allSettled([p1, p2, p3])
.then(results => {
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`${i}: ${result.value}`);
} else {
console.log(`${i}: ${result.reason.message}`);
}
});
});
// 0: 1
// 1: failed
// 2: 3
3.2 应用场景
批量操作的全量报告:
// 批量发送通知,不因单个失败而中断
const notifications = [
{ userId: 1, message: 'Hello' },
{ userId: 2, message: 'Hi' },
{ userId: 3, message: 'Hey' }
];
const results = await Promise.allSettled(
notifications.map(n => sendNotification(n))
);
// 统计成功和失败
const successes = results.filter(r => r.status === 'fulfilled').length;
const failures = results.filter(r => r.status === 'rejected').length;
console.log(`Sent ${successes}, failed ${failures}`);
四、Promise.any
4.1 基本用法
ES2021 引入。Promise.any 返回第一个成功的 Promise,忽略失败:
const p1 = Promise.reject(new Error('failed 1'));
const p2 = Promise.resolve(2);
const p3 = Promise.reject(new Error('failed 3'));
Promise.any([p1, p2, p3])
.then(value => console.log(value)); // 2
4.2 AggregateError
当所有 Promise 都失败时,返回 AggregateError:
const p1 = Promise.reject(new Error('error 1'));
const p2 = Promise.reject(new Error('error 2'));
Promise.any([p1, p2])
.catch(err => {
console.log(err instanceof AggregateError); // true
console.log(err.errors); // [Error: error 1, Error: error 2]
});
4.3 应用场景
竞速获取资源:
// 从多个 CDN 获取资源,使用最先响应的
const cdn1 = fetch('https://cdn1.example.com/lib.js');
const cdn2 = fetch('https://cdn2.example.com/lib.js');
const cdn3 = fetch('https://cdn3.example.com/lib.js');
const response = await Promise.any([cdn1, cdn2, cdn3]);
五、Promise 组合模式
5.1 并发限制
控制并发数量的模式:
async function parallelLimit(tasks, limit) {
const results = [];
const executing = new Set();
for (const task of tasks) {
const promise = Promise.resolve().then(() => task());
results.push(promise);
if (executing.size >= limit) {
const done = Promise.race(executing);
executing.add(done);
done.then(() => executing.delete(done));
}
}
return Promise.all(results);
}
5.2 顺序执行
// reduce 实现顺序执行
async function sequential(tasks) {
return tasks.reduce(async (acc, task) => {
const results = await acc;
const result = await task();
return [...results, result];
}, Promise.resolve([]));
}
5.3 重试机制
async function withRetry(fn, maxAttempts = 3, delay = 1000) {
for (let i = 0; i < maxAttempts; i++) {
try {
return await fn();
} catch (err) {
if (i === maxAttempts - 1) throw err;
await new Promise(r => setTimeout(r, delay));
}
}
}
六、面试高频考点
考点 1:Promise.all 的失败模型
Promise.all 采取" fail-fast"模型:任何一个 Promise reject,整体立即 reject。
考点 2:Promise.all vs Promise.allSettled
Promise.all 在任何一个失败时整体失败;Promise.allSettled 等待所有结束,收集每个的详细结果。
考点 3:Promise.race vs Promise.any
Promise.race 返回最先完成的结果(成功或失败);Promise.any 返回最先成功的结果,全部失败时抛出 AggregateError。