Skip to main content

How to Handle Multiple try...catch Blocks in JavaScript

Error handling is a critical part of writing robust applications. While a single try...catch block is great for handling one potential failure, you often need to manage errors from different sources or handle different types of errors in distinct ways.

This guide will explain the two primary, modern patterns for handling multiple errors: using a single try...catch block with conditional logic (recommended for related errors), and using separate try...catch blocks for independent operations.

The Core Task: Handling Different Types of Errors

Imagine a function that fetches data from an API. Several things could go wrong: the network could be down, the server could return a "Not Found" error, or the data could be malformed. You might want to handle each of these failures differently.

This is the most common and recommended best practice for handling different types of errors that can occur within a single, related operation. You wrap the entire operation in one try block, and then use the catch block to inspect the error and decide what to do.

The logic:

  1. Wrap the entire sequence of potentially failing operations in a single try block.
  2. In the catch block, use an if/else if chain or a switch statement to check the type or properties of the caught error object.
  3. Execute the specific error-handling logic for the matched error type.

The solution:

async function fetchData() {
try {
const response = await fetch('/api/user');

if (response.status === 404) {
throw new Error('User not found');
}
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json(); // This can also throw an error
return data;

} catch (error) {
// Check the error's message or name to determine its type
if (error.message === 'User not found') {
console.error('Handling a 404 error specifically...');
// Show a "User Not Found" message in the UI
} else if (error instanceof SyntaxError) {
console.error('Handling a JSON parsing error...');
// Show a "Malformed data" message
} else {
console.error('Handling a generic or network error:', error.message);
// Show a generic "Something went wrong" message
}
}
}
note

This pattern keeps the code for the "happy path" clean inside the try block and centralizes all error-handling logic in a single catch block.

Pattern 2: Separate try...catch Blocks for Independent Operations

If you have two or more completely independent operations and you want the second operation to run even if the first one fails, you should wrap each one in its own try...catch block.

Solution:

async function initializeDashboard() {
// Operation 1: Fetch user data
try {
const userData = await fetch('/api/user');
console.log('User data loaded:', await userData.json());
} catch (error) {
console.error('Failed to load user data:', error.message);
}

// Operation 2: Fetch site settings
// This block will run regardless of whether the first one succeeded or failed.
try {
const settingsData = await fetch('/api/settings');
console.log('Settings data loaded:', await settingsData.json());
} catch (error) {
console.error('Failed to load settings:', error.message);
}
}

initializeDashboard();

A Note on Custom Errors

For even cleaner conditional logic in your catch block, you can create and throw your own custom error classes. This allows you to use the instanceof operator for very readable type checking.

// Define custom error types
class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = 'NotFoundError';
}
}

// ... inside a catch block
catch (error) {
if (error instanceof NotFoundError) {
// Handle the "not found" case
} else {
// Handle other errors
}
}

A Common Pitfall: Nested try...catch

While syntactically valid, nesting try...catch blocks is often a sign of overly complex or "smelly" code. It can make the control flow very hard to follow.

The Not Recommended Pattern:

try {
// Outer operation
try {
// Inner operation
} catch (innerError) {
// Handle inner error
}
} catch (outerError) {
// Handle outer error
}

Solution: In most cases, nested try...catch blocks can be refactored into a single try...catch block with conditional logic (Pattern 1) or into separate, sequential try...catch blocks (Pattern 2).

6. Conclusion

Handling multiple errors in JavaScript is a matter of choosing the right pattern for your needs.

  • For handling different types of errors from a single, related operation, the recommended best practice is to use a single try...catch block and inspect the error with an if/else if chain or instanceof.
  • For handling errors from independent operations where one should not block the other, use separate, sequential try...catch blocks.
  • Avoid nested try...catch blocks whenever possible, as they can make your code difficult to read and maintain.