JavaScript Promise.allSettled() 方法详解:等待所有Promise完成
处理多个异步任务时,有时候我们并不关心每个任务是成功还是失败,只想知道所有任务都执行完了,然后根据每个结果做相应处理。这种场景下,Promise.allSettled() 就是最合适的选择。
Promise.allSettled() 是什么?
Promise.allSettled() 接收一个Promise数组,等待所有Promise都完成(无论是成功还是失败),然后返回一个数组,包含每个Promise的结果状态。
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
});
基本语法
Promise.allSettled(iterable);
-
iterable:一个可迭代对象,是Promise数组
-
返回值:一个新的Promise,它会等所有输入Promise都完成(settled)后,解析为一个结果数组
-
结果数组:每个元素都是一个对象,包含:
-
status:'fulfilled' 或 'rejected' -
value:如果成功,这里是对应的值 -
reason:如果失败,这里是错误原因
-
基础示例
代码号学习: 多个异步任务状态监控
const task1 = Promise.resolve('任务1完成');
const task2 = Promise.reject('任务2出错');
const task3 = new Promise(resolve => {
setTimeout(() => resolve('任务3完成'), 1000);
});
Promise.allSettled([task1, task2, task3])
.then(results => {
console.log('所有任务执行完毕,结果如下:');
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`任务${index + 1} 成功:`, result.value);
} else {
console.log(`任务${index + 1} 失败:`, result.reason);
}
});
});
// 输出:
// 所有任务执行完毕,结果如下:
// 任务1 成功: 任务1完成
// 任务2 失败: 任务2出错
// 任务3 成功: 任务3完成
实际应用场景
场景1:批量数据导入,记录成功和失败
// 模拟导入单个数据项
function importDataItem(item) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 随机成功或失败
if (Math.random() > 0.3) {
resolve(`✔ 导入成功: ${item.name}`);
} else {
reject(`✘ 导入失败: ${item.name} (数据格式错误)`);
}
}, Math.random() * 1000);
});
}
// 批量导入数据
async function batchImport(dataList) {
console.log(`开始批量导入,共 ${dataList.length} 条数据`);
const promises = dataList.map(item => importDataItem(item));
const results = await Promise.allSettled(promises);
// 统计结果
const summary = {
total: results.length,
success: 0,
failed: 0,
details: []
};
results.forEach(result => {
if (result.status === 'fulfilled') {
summary.success++;
summary.details.push(result.value);
} else {
summary.failed++;
summary.details.push(result.reason);
}
});
console.log('导入完成统计:', summary);
return summary;
}
// 使用
const dataToImport = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
{ id: 4, name: '赵六' }
];
batchImport(dataToImport).then(summary => {
if (summary.failed > 0) {
console.log(`有 ${summary.failed} 条数据导入失败,请检查日志`);
}
});
场景2:健康检查多个服务
// 检查单个服务状态
function checkServiceHealth(serviceName, url) {
return fetch(url)
.then(response => {
if (response.ok) {
return {
service: serviceName,
status: 'healthy',
time: new Date().toLocaleTimeString()
};
} else {
throw new Error(`HTTP ${response.status}`);
}
})
.catch(error => {
throw new Error(`${serviceName} 服务异常: ${error.message}`);
});
}
// 批量检查所有服务
async function healthCheckAll() {
const services = [
{ name: '用户服务', url: '/api/users/health' },
{ name: '订单服务', url: '/api/orders/health' },
{ name: '支付服务', url: '/api/payment/health' },
{ name: '消息服务', url: '/api/notification/health' }
];
const checks = services.map(s =>
checkServiceHealth(s.name, s.url)
.then(data => data)
.catch(error => ({
service: s.name,
status: 'unhealthy',
error: error.message
}))
);
// 用 allSettled 确保所有检查都完成
const results = await Promise.allSettled(checks);
// 整理报告
const report = {
timestamp: new Date().toISOString(),
services: []
};
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
report.services.push(result.value);
} else {
// 理论上这里不会执行,因为上面已经catch了
report.services.push({
service: services[index].name,
status: 'unknown',
error: '检查过程出错'
});
}
});
console.log('服务健康检查报告:', report);
return report;
}
// 定时执行健康检查
setInterval(() => {
healthCheckAll().then(report => {
// 如果有服务异常,发送告警
const unhealthy = report.services.filter(s => s.status !== 'healthy');
if (unhealthy.length > 0) {
console.warn('发现异常服务:', unhealthy);
sendAlert(unhealthy);
}
});
}, 60000); // 每分钟检查一次
场景3:批量文件上传,显示每个文件状态
class FileUploader {
constructor() {
this.results = [];
}
uploadFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
// 模拟上传过程
setTimeout(() => {
if (file.size < 10 * 1024 * 1024) { // 小于10MB
resolve({
fileName: file.name,
size: file.size,
message: '上传成功'
});
} else {
reject({
fileName: file.name,
size: file.size,
message: '文件过大,请压缩后重试'
});
}
}, 1000);
};
reader.onerror = () => reject({
fileName: file.name,
message: '文件读取失败'
});
reader.readAsArrayBuffer(file);
});
}
async uploadMultiple(files) {
console.log(`开始上传 ${files.length} 个文件...`);
const uploadPromises = Array.from(files).map(file =>
this.uploadFile(file)
.then(result => ({ status: 'fulfilled', value: result }))
.catch(error => ({ status: 'rejected', reason: error }))
);
// 用 allSettled 等待所有上传完成
const results = await Promise.allSettled(uploadPromises);
// 处理最终结果
const finalResults = results.map(r => r.value || r.reason);
// 统计
const successCount = finalResults.filter(r => r.status === 'fulfilled' || !r.status).length;
const failCount = finalResults.filter(r => r.status === 'rejected' || r.reason).length;
console.log(`
上传完成:
- 成功: ${successCount} 个
- 失败: ${failCount} 个
`);
return finalResults;
}
}
// HTML中使用
// <input type="file" id="fileInput" multiple>
document.getElementById('fileInput').addEventListener('change', async (e) => {
const uploader = new FileUploader();
const results = await uploader.uploadMultiple(e.target.files);
results.forEach(result => {
if (result.value) {
console.log('✔', result.value.fileName);
} else {
console.log('✘', result.reason.fileName, '-', result.reason.message);
}
});
});
Promise.allSettled() 与他方法的对比
| 方法 | 行为 | 适用场景 |
|---|---|---|
| Promise.allSettled() | 等待所有完成,返回每个的结果 | 需要知道所有任务的结果,不管成功失败 |
| Promise.all() | 全部成功才返回,一个失败就整体失败 | 多个并行请求,缺一不可 |
| Promise.race() | 返回第一个完成的结果 | 超时控制、竞速 |
| Promise.any() | 返回第一个成功的结果 | 只需要一个成功的,不关心他 |
// 对比示例
const p1 = Promise.resolve('成功1');
const p2 = Promise.reject('失败2');
const p3 = new Promise(resolve => setTimeout(() => resolve('成功3'), 1000));
// Promise.allSettled - 等待所有,返回每个结果
Promise.allSettled([p1, p2, p3])
.then(results => console.log('allSettled:', results));
// Promise.all - 遇到失败就整体失败
Promise.all([p1, p2, p3])
.then(results => console.log('all成功:', results))
.catch(error => console.log('all失败:', error)); // 会执行这里
// Promise.race - 返回第一个完成的(p2最快失败)
Promise.race([p1, p2, p3])
.then(result => console.log('race成功:', result))
.catch(error => console.log('race失败:', error)); // race失败: 失败2
// Promise.any - 返回第一个成功的(p1成功)
Promise.any([p2, p1, p3])
.then(result => console.log('any成功:', result)); // any成功: 成功1
个人经验:在项目中最常用 allSettled 的场景是批量操作的状态记录。比如批量发送邮件,有些成功有些失败,我需要知道具体哪些失败以及失败原因,而不是整体成功或失败。如果用 Promise.all,一个失败就全断了,根本拿不到完整的结果。
处理空数组
// 传入空数组时,立即返回空结果数组
Promise.allSettled([])
.then(results => {
console.log('空数组结果:', results); // []
});
// 传入包含非Promise值的数组,会自动转换为 Promise.resolve
Promise.allSettled([1, 'hello', true])
.then(results => {
results.forEach(r => {
console.log(r.status, r.value);
});
// 输出:
// fulfilled 1
// fulfilled hello
// fulfilled true
});
实际项目中的封装
// 封装一个通用的批量处理函数
async function batchProcessor(tasks, options = {}) {
const {
concurrency = 5, // 并发数
onProgress = null, // 进度回调
onTaskComplete = null // 单个任务完成回调
} = options;
const results = [];
const chunks = [];
// 按并发数分组
for (let i = 0; i < tasks.length; i += concurrency) {
chunks.push(tasks.slice(i, i + concurrency));
}
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
console.log(`处理第 ${i + 1}/${chunks.length} 批,共 ${chunk.length} 个任务`);
// 处理当前批次
const chunkPromises = chunk.map(task =>
typeof task === 'function' task() : task
);
const chunkResults = await Promise.allSettled(chunkPromises);
// 记录结果
chunkResults.forEach((result, index) => {
const taskIndex = i * concurrency + index;
results[taskIndex] = result;
if (onTaskComplete) {
onTaskComplete(taskIndex, result);
}
});
// 进度回调
if (onProgress) {
onProgress((i + 1) * concurrency, tasks.length);
}
}
// 统计
const summary = {
total: results.length,
fulfilled: results.filter(r => r.status === 'fulfilled').length,
rejected: results.filter(r => r.status === 'rejected').length,
results
};
return summary;
}
// 使用示例
const tasks = [
() => fetch('/api/data1'),
() => fetch('/api/data2'),
() => Promise.reject('模拟失败'),
() => new Promise(resolve => setTimeout(() => resolve('慢速任务'), 2000)),
// ... 更多任务
];
batchProcessor(tasks, {
concurrency: 3,
onProgress: (completed, total) => {
console.log(`进度: ${completed}/${total}`);
},
onTaskComplete: (index, result) => {
console.log(`任务${index} ${result.status}`);
}
}).then(summary => {
console.log('处理完成:', summary);
// 可以在这里处理失败的任务
summary.results.forEach((result, index) => {
if (result.status === 'rejected') {
console.log(`任务${index} 失败原因:`, result.reason);
// 记录日志、重试等
}
});
});
课程知识要点
-
核心功能:Promise.allSettled() 等待所有Promise完成,返回每个的结果状态
-
结果格式:每个结果包含
status字段('fulfilled'/'rejected'),成功时有value,失败时有reason -
适用场景:需要知道所有异步任务的结果,无论成功失败
-
典型应用:批量数据导入、批量文件上传、服务健康检查、日志记录
-
与他方法区别:allSettled 不短路,会等所有任务完成;all 遇到失败就短路
-
空数组处理:返回空结果数组
-
非Promise值:自动包装为 Promise.resolve()