← JavaScript Promise.any() JavaScript Promise.finally() →

JavaScript Promise.allSettled()

原创 2026-03-14 JavaScript 已有人查阅

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()

← JavaScript Promise.any() JavaScript Promise.finally() →
分享笔记 (共有 篇笔记)
验证码:
微信公众号