πŸš€Debugging Microservices & Distributed Systems
5 min read
advanced

AggregateError in JavaScript

AggregateError helps you handle multiple errors at once in JavaScript. This makes your code easier to manage and more reliable.

A few days ago, I was reviewing all the error types in JavaScript and came across something relatively new: AggregateError

Error TypeDescription
ErrorBase error object for generic errors.
SyntaxErrorThrown when there is a syntax mistake in the code.
ReferenceErrorThrown when referencing a variable that doesn’t exist.
TypeErrorThrown when a value is not of the expected type.
RangeErrorThrown when a number is outside an allowable range.
URIErrorThrown when there is an issue with encoding/decoding URI components.
EvalErrorHistorical, used for improper eval() usage (mostly obsolete).
AggregateErrorThrown when multiple errors need to be handled together. (added in ECMAScript 2021)

AggregateError was added in ECMAScript 2021 (ES12). It’s designed to help when you need to deal with multiple errors at once. This is super handy in scenarios like working with promises, where you might want to handle all errors together instead of one by one.

If you use Promise.any() to find the first successful promise, but they all fail, JavaScript will throw an AggregateError. This error lists all the reasons why the promises failed.

const fetchFromApi1 = () => Promise.reject(new Error('API 1 failed'));
const fetchFromApi2 = () => Promise.reject(new Error('API 2 failed'));
const fetchFromApi3 = () => Promise.reject(new Error('API 3 failed'));
async function fetchWithRetry(apis, retries = 3, delay = 1000) {
try {
const data = await Promise.any(apis);
console.log('Data fetched:', data);
return data;
} catch (e) {
if (e instanceof AggregateError) {
console.log(e.name); // "AggregateError"
console.log(e.message); // "All promises were rejected"
console.log('Errors:', e.errors); // [Error: API 1 failed, Error: API 2 failed, Error: API 3 failed]
if (retries > 0) {
console.log(`Retrying... (${retries} attempts left)`);
await new Promise(resolve => setTimeout(resolve, delay));
return fetchWithRetry(apis, retries - 1, delay);
} else {
console.log('All data sources failed after multiple attempts. Please try again later.');
throw e; // Re-throw the error after exhausting retries
}
} else {
// If the error is not an AggregateError, rethrow it
throw e;
}
}
}
// Start fetching data with retries
fetchWithRetry([fetchFromApi1(), fetchFromApi2(), fetchFromApi3()]);

Breakdown

  • I use Promise.any() to try fetching data from multiple APIs, resolving with the first successful result. If all promises fail, it throws an AggregateError.

  • I handle the potential errors with a try/catch block, where the try block deals with successful data fetching, and the catch block catches the AggregateError if all promises are rejected.

  • If an AggregateError occurs, I log the error details and then attempt to retry the operation up to a specified number of times, with a delay between each retry.

  • I implement the retry logic by recursively calling the function, reducing the retry count each time, until either I get a successful result or exhaust all retries.

  • If all retries fail, I log a final error message and rethrow the AggregateError so it can be handled further if needed.

AggregateError simplifies error handling when you’re dealing with multiple failures at once, especially in asynchronous code. It’s one of those features that might not seem necessary until you run into a situation where it saves you a ton of headaches.

Without AggregateError

To illustrate the usefulness of AggregateError, I’ll show how handling multiple promise rejections without it can be cumbersome and less efficient. Here’s how you might handle this situation without AggregateError

const fetchFromApi1 = () => Promise.reject(new Error('API 1 failed'));
const fetchFromApi2 = () => Promise.reject(new Error('API 2 failed'));
const fetchFromApi3 = () => Promise.reject(new Error('API 3 failed'));
async function fetchWithoutAggregateError(apis, retries = 3, delay = 1000) {
try {
const data = await Promise.any(apis);
console.log('Data fetched:', data);
return data;
} catch (e) {
console.log('All promises failed.');
// Manually handle each promise rejection by checking which promises failed
for (let i = 0; i < apis.length; i++) {
try {
await apis[i];
} catch (error) {
console.log(`Error from API ${i + 1}:`, error.message);
}
}
if (retries > 0) {
console.log(`Retrying... (${retries} attempts left)`);
await new Promise(resolve => setTimeout(resolve, delay));
return fetchWithoutAggregateError(apis, retries - 1, delay);
} else {
console.log('All data sources failed after multiple attempts. Please try again later.');
throw new Error('All data sources failed after retries.');
}
}
}
// Start fetching data with retries
fetchWithoutAggregateError([fetchFromApi1(), fetchFromApi2(), fetchFromApi3()]);

When Promise.any() rejects, I don’t have an AggregateError to easily access all the errors. Instead, I loop through the original promises and individually check which ones failed by attempting to await them again, logging each failure.

function validateUserWithoutAggregateError(user) {
if (!user.name) {
throw new Error('Name is required');
}
if (!user.email) {
throw new Error('Email is required');
}
if (user.age < 18) {
throw new Error('User must be at least 18 years old');
}
return true;
}
try {
validateUserWithoutAggregateError({ name: '', email: '', age: 17 });
} catch (e) {
console.log(e.message); // Only the first error is caught and displayed
// Output: "Name is required"
}

If you don’t use AggregateError, you might have to throw and handle each error individually, which complicates the error-handling logic.

Problems Without AggregateError

  • Limited Feedback: The function stops at the first error, so the user only sees one error message at a time. This can be frustrating for users, as they have to fix one issue and submit the form again to see the next error.
  • Cumbersome Code: Handling each error individually requires repetitive code if you want to collect and display all errors at once.

With AggregateError

WithΒ AggregateError, error handling is centralized and simplified. It automatically collects all the rejections, allowing you to manage them in a more structured way by grouping multiple errors instead of dealing with each one separately.

function validateUser(user) {
let errors = [];
if (!user.name) {
errors.push(new Error('Name is required'));
}
if (!user.email) {
errors.push(new Error('Email is required'));
}
if (user.age < 18) {
errors.push(new Error('User must be at least 18 years old'));
}
if (errors.length > 0) {
throw new AggregateError(errors, 'Validation failed');
}
return true;
}
try {
validateUser({ name: '', email: '', age: 17 });
} catch (e) {
if (e instanceof AggregateError) {
console.log(e.name); // "AggregateError"
console.log(e.message); // "Validation failed"
e.errors.forEach(err => console.log(err.message));
// Output:
// "Name is required"
// "Email is required"
// "User must be at least 18 years old"
}
}

Benefits of Using AggregateError

  • Consolidated Error Reporting: All validation errors are collected and thrown together. The user gets feedback on all issues at once, which improves the user experience.
  • Simpler Error Handling: Instead of handling multiple individual errors, you only need one try/catch block to handle them all.
  • Complete Debugging Information: The AggregateError contains a list of all the errors, making it easier to debug and understand what went wrong.

Using AggregateError to group multiple errors has several advantages. It’s especially helpful when multiple things can go wrong at once, like during form validation or parallel API requests.


Related Articles

If you enjoyed this article, you might find these related pieces interesting as well.

Recommended Engineering Resources

Here are engineering resources I've personally vetted and use. They focus on skills you'll actually need to build and scale real projects - the kind of experience that gets you hired or promoted.

Imagine where you would be in two years if you actually took the time to learn every day. A little effort consistently adds up, shaping your skills, opening doors, and building the career you envision. Start now, and future you will thank you.


This article was originally published on https://www.trevorlasn.com/blog/aggregate-error-in-javascript. It was written by a human and polished using grammar tools for clarity.

Interested in a partnership? Shoot me an email at hi [at] trevorlasn.com with all relevant information.