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. 促进了更好的错误处理模式 - 鼓励开发者考虑和处理所有可能的错误情况
相关推荐
一枚前端小能手5 小时前
🌐 HTML DOM API全攻略(下篇)- 高级接口与现代Web开发实践
前端·javascript·html
IT_陈寒5 小时前
React性能翻倍!3个90%开发者不知道的Hooks优化技巧 🚀
前端·人工智能·后端
CC码码5 小时前
前端2D地图和3D场景中的坐标系
前端·3d·js
慧一居士6 小时前
Vue 中 <keep-alive> 功能介绍,使用场景,完整使用示例演示
前端·vue.js
xixixin_6 小时前
【React】节流会在react内失效??
开发语言·前端·javascript·react
I like Code?6 小时前
Ant Design Landing模版使用教程-react-npm
前端·react.js·npm
光影少年6 小时前
React Navite 第二章
前端·react native·react.js·前端框架
晴殇i6 小时前
解锁Web Workers:解决90%前端性能瓶颈的利器
前端·javascript·vue.js
@PHARAOH6 小时前
HOW - React 状态模块化管理和按需加载(二)- 不同状态库哲学
前端·react.js·前端框架