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. 促进了更好的错误处理模式 - 鼓励开发者考虑和处理所有可能的错误情况
相关推荐
卸任4 小时前
Vibe coding工具使用心得
前端·ai编程
brien95 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance5 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M6 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写6 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李6 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易6 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
清晨Coding6 小时前
删了 Cookie 还能自动登录?Google系网站登录态自动恢复的原理分析
javascript
风骏时光牛马6 小时前
前沿视界
前端
kyriewen6 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员