AggregateError:JavaScript 中的聚合错误处理

概述

AggregateError 是 JavaScript 中一个相对较新的错误类型,文章最后有兼容性介绍,它允许将多个错误包装在一个错误对象中。这个特性在处理多个可能失败的异步操作时特别有用,能够提供更清晰和结构化的错误处理机制。

为什么需要 AggregateError?

在异步编程中,我们经常需要同时处理多个操作:

javascript 复制代码
// 传统方式 - 难以处理多个错误
try {
    const result1 = await asyncOperation1();
    const result2 = await asyncOperation2();
    const result3 = await asyncOperation3();
} catch (error) {
    // 只能捕获第一个错误
    console.log(error);
}

使用 Promise.all() 时,如果多个 Promise 被拒绝,只能得到第一个错误:

javascript 复制代码
Promise.all([
    Promise.reject(new Error('错误1')),
    Promise.reject(new Error('错误2')),
    Promise.resolve('成功')
]).catch(error => {
    // 只能得到 '错误1'
    console.log(error.message);
});

AggregateError 解决了这个问题,它让我们能够收集和处理所有的错误。

语法

创建 AggregateError

javascript 复制代码
new AggregateError(errors, message)
  • errors:一个可迭代对象(通常是数组),包含要聚合的错误
  • message:可选的错误描述信息

基本示例

javascript 复制代码
// 创建 AggregateError
const error1 = new Error('网络错误');
const error2 = new TypeError('类型错误');
const error3 = new RangeError('范围错误');

const aggregateError = new AggregateError(
    [error1, error2, error3],
    '多个操作失败'
);

console.log(aggregateError.message); // '多个操作失败'
console.log(aggregateError.errors); // [Error, TypeError, RangeError]
console.log(aggregateError.errors[0].message); // '网络错误'

主要使用场景

1. 与 Promise.any() 结合使用

Promise.any() 在所有 Promise 都被拒绝时抛出 AggregateError

javascript 复制代码
const promises = [
    Promise.reject(new Error('失败1')),
    Promise.reject(new Error('失败2')),
    Promise.reject(new Error('失败3'))
];

Promise.any(promises)
    .then(result => {
        console.log('成功:', result);
    })
    .catch(error => {
        if (error instanceof AggregateError) {
            console.log('所有操作都失败了:');
            error.errors.forEach((err, index) => {
                console.log(`错误 ${index + 1}: ${err.message}`);
            });
        } else {
            console.log('其他错误:', error.message);
        }
    });

2. 批量操作错误收集

javascript 复制代码
async function processMultipleOperations(operations) {
    const results = await Promise.allSettled(operations);
    
    const errors = results
        .filter(result => result.status === 'rejected')
        .map(result => result.reason);
    
    if (errors.length > 0) {
        throw new AggregateError(errors, `${errors.length} 个操作失败`);
    }
    
    return results.map(result => result.value);
}

// 使用示例
const operations = [
    fetch('/api/data1'),
    fetch('/api/data2'),
    fetch('/api/data3')
];

processMultipleOperations(operations)
    .then(data => {
        console.log('所有操作成功:', data);
    })
    .catch(error => {
        if (error instanceof AggregateError) {
            console.log('失败的操作:');
            error.errors.forEach(err => {
                console.log('-', err.message);
            });
        }
    });

3. 数据验证

javascript 复制代码
function validateUserData(userData) {
    const errors = [];
    
    if (!userData.email || !userData.email.includes('@')) {
        errors.push(new Error('邮箱格式不正确'));
    }
    
    if (!userData.password || userData.password.length < 8) {
        errors.push(new Error('密码必须至少8个字符'));
    }
    
    if (!userData.age || userData.age < 18) {
        errors.push(new Error('年龄必须大于18岁'));
    }
    
    if (errors.length > 0) {
        throw new AggregateError(errors, '用户数据验证失败');
    }
    
    return true;
}

// 使用
try {
    validateUserData({
        email: 'invalid-email',
        password: '123',
        age: 16
    });
} catch (error) {
    if (error instanceof AggregateError) {
        console.log('验证错误:');
        error.errors.forEach(err => {
            console.log(`• ${err.message}`);
        });
    }
}

实际应用示例

文件上传批量处理

javascript 复制代码
class FileUploader {
    async uploadFiles(files) {
        const uploadPromises = files.map(file => this.uploadSingleFile(file));
        const results = await Promise.allSettled(uploadPromises);
        
        const errors = [];
        const successfulUploads = [];
        
        results.forEach((result, index) => {
            if (result.status === 'rejected') {
                errors.push({
                    file: files[index].name,
                    error: result.reason
                });
            } else {
                successfulUploads.push({
                    file: files[index].name,
                    url: result.value
                });
            }
        });
        
        if (errors.length > 0) {
            const uploadErrors = errors.map(
                err => new Error(`文件 ${err.file} 上传失败: ${err.error.message}`)
            );
            throw new AggregateError(uploadErrors, '部分文件上传失败');
        }
        
        return successfulUploads;
    }
    
    async uploadSingleFile(file) {
        // 模拟上传逻辑
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (file.size > 5 * 1024 * 1024) { // 5MB
                    reject(new Error('文件大小超过限制'));
                } else if (!file.type.startsWith('image/')) {
                    reject(new Error('只支持图片文件'));
                } else {
                    resolve(`https://example.com/uploads/${file.name}`);
                }
            }, 1000);
        });
    }
}

// 使用
const uploader = new FileUploader();
const files = [
    { name: 'photo1.jpg', size: 1024000, type: 'image/jpeg' },
    { name: 'large-video.mp4', size: 10 * 1024 * 1024, type: 'video/mp4' },
    { name: 'document.pdf', size: 500000, type: 'application/pdf' }
];

uploader.uploadFiles(files)
    .then(uploads => {
        console.log('成功上传的文件:', uploads);
    })
    .catch(error => {
        if (error instanceof AggregateError) {
            console.log('上传失败的文件:');
            error.errors.forEach(err => {
                console.log(`- ${err.message}`);
            });
            
            console.log(`成功上传了 ${files.length - error.errors.length} 个文件`);
        } else {
            console.log('未知错误:', error.message);
        }
    });

浏览器兼容性

AggregateError 的兼容性如下:

兼容性处理

对于不支持的环境,可以使用 polyfill:

javascript 复制代码
// 简单的 AggregateError polyfill
if (typeof AggregateError === 'undefined') {
    class AggregateError extends Error {
        constructor(errors, message) {
            super(message);
            this.name = 'AggregateError';
            this.errors = errors;
        }
    }
    window.AggregateError = AggregateError;
}

// 或者使用 core-js polyfill
import 'core-js/actual/aggregate-error';

特性检测

javascript 复制代码
function supportsAggregateError() {
    try {
        new AggregateError([]);
        return true;
    } catch {
        return false;
    }
}

if (!supportsAggregateError()) {
    // 降级方案
    class SimpleAggregateError extends Error {
        constructor(errors, message) {
            super(message);
            this.name = 'AggregateError';
            this.errors = errors;
        }
    }
    
    // 替换原生的 AggregateError
    window.AggregateError = SimpleAggregateError;
}

最佳实践

1. 总是检查错误类型

javascript 复制代码
try {
    // 可能抛出 AggregateError 的代码
} catch (error) {
    if (error instanceof AggregateError) {
        // 处理聚合错误
        console.log('多个错误:', error.errors.length);
    } else {
        // 处理单个错误
        console.log('单个错误:', error.message);
    }
}

2. 提供有意义的错误信息

javascript 复制代码
// 好的做法
throw new AggregateError(
    errors,
    `操作失败: ${errors.length} 个子操作出错`
);

// 不好的做法
throw new AggregateError(errors, '错误');

3. 保持错误结构一致

javascript 复制代码
function createAggregateError(failures, context) {
    const errors = failures.map(failure => 
        new Error(`${context}: ${failure.message}`)
    );
    return new AggregateError(errors, `${context} - ${failures.length} 个失败`);
}

总结

AggregateError对象代表了包装了多个错误对象的单个错误对象,它:

  1. 提供了结构化的多错误处理 - 能够将多个相关错误组织在一起
  2. 改善了异步编程体验 - 特别是在使用 Promise.any() 和批量操作时
  3. 增强了错误信息的可读性 - 通过聚合相关的错误信息
  4. 促进了更好的错误处理模式 - 鼓励开发者考虑和处理所有可能的错误情况
相关推荐
veneno5 小时前
大量异步并发请求控制并发解决方案
前端
i***t9195 小时前
Spring Boot项目接收前端参数的11种方式
前端·spring boot·后端
oden6 小时前
2025博客框架选择指南:Hugo、Astro、Hexo该选哪个?
前端·html
小光学长6 小时前
基于ssm的宠物交易系统的设计与实现850mb48h(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·前端·数据库
云中飞鸿6 小时前
函数:委托
javascript
小小前端要继续努力6 小时前
渐进增强、优雅降级及现代Web开发技术详解
前端
老前端的功夫7 小时前
前端技术选型的理性之道:构建可量化的ROI评估模型
前端·javascript·人工智能·ubuntu·前端框架
狮子座的男孩7 小时前
js函数高级:04、详解执行上下文与执行上下文栈(变量提升与函数提升、执行上下文、执行上下文栈)及相关面试题
前端·javascript·经验分享·变量提升与函数提升·执行上下文·执行上下文栈·相关面试题
爱学习的程序媛7 小时前
《JavaScript权威指南》核心知识点梳理
开发语言·前端·javascript·ecmascript
乐观主义现代人8 小时前
go 面试
java·前端·javascript