What is try...catch for?

👨‍💻 Frontend Developer 🟠 May come up 🎚️ Medium
#JavaScript #Asynchronicity #JS Basics

Brief Answer

Try…catch is a JavaScript construct for error handling that allows catching and handling exceptions that occur during code execution. The try block contains code that might cause an error, while the catch block contains code to handle that error. The optional finally block executes regardless of whether an error occurred or not.

Key benefits:

  • Preventing application crashes — errors don’t stop the entire program
  • Centralized error handling — all errors are handled in one place
  • Improved user experience — ability to show user-friendly error messages

Full Answer

Try…catch is a fundamental error handling construct in JavaScript that allows gracefully handling exceptions and preventing program crashes. This is especially important in asynchronous code and when working with external APIs.

How try…catch Works

The try…catch construct consists of three parts:

try {
  // Code that might cause an error
  const result = riskyOperation();
  console.log(result);
} catch (error) {
  // Error handling
  console.error('An error occurred:', error.message);
} finally {
  // Optional block that executes in any case
  console.log('Resource cleanup');
}

Main Use Cases

1. Handling JSON Errors

Commonly used when parsing JSON data:

function parseUserData(jsonString) {
  try {
    const userData = JSON.parse(jsonString);
    return userData;
  } catch (error) {
    if (error instanceof SyntaxError) {
      console.error('Invalid JSON format:', error.message);
      return null;
    } else {
      throw error; // Re-throw other types of errors
    }
  }
}
 
// Usage
const validJson = '{"name": "John", "age": 30}';
const invalidJson = '{"name": "John", "age":}'; // Invalid JSON
 
console.log(parseUserData(validJson));   // {name: "John", age: 30}
console.log(parseUserData(invalidJson)); // null (with error message)

2. Handling API Errors

Important when making HTTP requests:

async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    
    const userData = await response.json();
    return userData;
  } catch (error) {
    if (error instanceof TypeError) {
      console.error('Network error:', error.message);
    } else if (error.message.includes('HTTP error')) {
      console.error('Server error:', error.message);
    } else {
      console.error('Unknown error:', error.message);
    }
    
    // Return default value
    return { id: userId, name: 'User not found' };
  }
}

3. Handling localStorage Errors

Useful when working with browser storage:

function saveToLocalStorage(key, data) {
  try {
    localStorage.setItem(key, JSON.stringify(data));
    console.log('Data saved successfully');
  } catch (error) {
    if (error instanceof DOMException) {
      if (error.name === 'QuotaExceededError') {
        console.error('Storage quota exceeded');
      } else {
        console.error('Storage access error:', error.message);
      }
    } else {
      console.error('Unknown error:', error.message);
    }
  }
}
 
function loadFromLocalStorage(key) {
  try {
    const data = localStorage.getItem(key);
    return data ? JSON.parse(data) : null;
  } catch (error) {
    console.error('Error loading data:', error.message);
    return null;
  }
}

Practical Examples

Data Validation with Error Handling

function validateUserInput(input) {
  try {
    // Check required fields
    if (!input.name || !input.email) {
      throw new Error('Required fields are missing');
    }
    
    // Check email format
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(input.email)) {
      throw new Error('Invalid email format');
    }
    
    // Check age
    if (input.age && (input.age < 0 || input.age > 150)) {
      throw new Error('Invalid age');
    }
    
    return { isValid: true, data: input };
  } catch (error) {
    return { isValid: false, error: error.message };
  }
}
 
// Usage
const validInput = { name: 'John', email: 'john@example.com', age: 25 };
const invalidInput = { name: 'John', email: 'invalid-email' };
 
console.log(validateUserInput(validInput));   // { isValid: true, ... }
console.log(validateUserInput(invalidInput)); // { isValid: false, error: "Invalid email format" }

Error Handling in Asynchronous Operations

async function processMultipleOperations() {
  const operations = [
    fetch('/api/data1'),
    fetch('/api/data2'),
    fetch('/api/data3')
  ];
  
  try {
    // Execute all operations in parallel
    const results = await Promise.all(operations.map(op => 
      op.catch(error => ({ error }))
    ));
    
    // Process results
    const successful = results.filter(result => !result.error);
    const failed = results.filter(result => result.error);
    
    console.log(`Successful: ${successful.length}, Errors: ${failed.length}`);
    
    return { successful, failed };
  } catch (error) {
    console.error('Critical error:', error.message);
    throw error;
  }
}

Error Handling with Custom Types

// Custom error class
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}
 
function processFormData(formData) {
  try {
    // Validation
    if (!formData.username) {
      throw new ValidationError('Username is required', 'username');
    }
    
    if (formData.username.length < 3) {
      throw new ValidationError('Username must be at least 3 characters', 'username');
    }
    
    if (!formData.password) {
      throw new ValidationError('Password is required', 'password');
    }
    
    // Process data
    return { success: true, data: formData };
  } catch (error) {
    if (error instanceof ValidationError) {
      return { 
        success: false, 
        error: error.message, 
        field: error.field 
      };
    } else {
      // Other error types
      return { 
        success: false, 
        error: 'An unknown error occurred' 
      };
    }
  }
}

Common Mistakes and Solutions

1. Ignoring Errors

// ❌ Ignoring errors
try {
  riskyOperation();
} catch (error) {
  // Empty catch block - bad practice
}
 
// ✅ Proper handling
try {
  riskyOperation();
} catch (error) {
  console.error('Error:', error.message);
  // Or send to logging system
  logError(error);
}

2. Catching All Errors the Same Way

// ❌ Catching all errors identically
try {
  riskyOperation();
} catch (error) {
  alert('An error occurred'); // Not informative
}
 
// ✅ Different handling for different error types
try {
  riskyOperation();
} catch (error) {
  if (error instanceof TypeError) {
    console.error('Type error:', error.message);
  } else if (error instanceof ReferenceError) {
    console.error('Reference error:', error.message);
  } else {
    console.error('Other error:', error.message);
  }
}

3. Forgotten finally

// ❌ Forgotten finally for resource cleanup
function processFile(filename) {
  let file;
  try {
    file = openFile(filename);
    return processData(file);
  } catch (error) {
    console.error('Error processing file:', error);
  }
  // File might remain open!
}
 
// ✅ Proper finally usage
function processFile(filename) {
  let file;
  try {
    file = openFile(filename);
    return processData(file);
  } catch (error) {
    console.error('Error processing file:', error);
    throw error;
  } finally {
    // Always close file
    if (file) {
      closeFile(file);
    }
  }
}

Best Practices

  1. Always handle errors — don’t leave empty catch blocks
  2. Use specific error types — differentiate between different exception types
  3. Log errors — send error information to logging systems
  4. Show user-friendly messages — don’t show technical details to users
  5. Use finally for resource cleanup — close connections, files, etc.
  6. Don’t catch errors unnecessarily — sometimes it’s better to let them fail

Comparison with Other Error Handling Methods

MethodAdvantagesDisadvantages
try…catchFlexible, clear syntaxCan hide errors
Promise.catch()For asynchronous operationsChains can be complex
window.onerrorGlobal handlingOnly for runtime errors
process.on(‘uncaughtException’)For Node.jsOnly for unhandled errors

Key try…catch Benefits

  1. Preventing application crashes — errors don’t stop execution
  2. Graceful degradation — application continues with limited functionality
  3. Improved debugging — ability to log errors
  4. Better UX — users see understandable messages
  5. Execution control — can make decisions based on error type

Try…catch is an important tool for creating reliable JavaScript applications. Understanding proper usage of this construct helps create more stable and predictable code.


Knowledge Check Task

Task

What will be output to the console and why? Fix the code if necessary:

function divide(a, b) {
  if (b === 0) {
    throw new Error('Division by zero');
  }
  return a / b;
}
 
function calculate() {
  try {
    console.log('1. Start calculation');
    
    const result1 = divide(10, 2);
    console.log('2. Result 1:', result1);
    
    const result2 = divide(10, 0);
    console.log('3. Result 2:', result2);
    
    const result3 = divide(20, 4);
    console.log('4. Result 3:', result3);
    
  } catch (error) {
    console.log('5. Error:', error.message);
  }
  
  console.log('6. End calculation');
}
 
calculate();
View answer

Answer: The following will be output to the console:

1. Start calculation
2. Result 1: 5
5. Error: Division by zero
6. End calculation

Explanation:

  1. console.log('1. Start calculation') executes
  2. divide(10, 2) is called — returns 5, “2. Result 1: 5” is output
  3. divide(10, 0) is called — throws Error(‘Division by zero’)
  4. Execution jumps to catch block, “5. Error: Division by zero” is output
  5. Code after the error line in try block doesn’t execute (result3 won’t be calculated)
  6. Code after try…catch block executes, “6. End calculation” is output

Fixed version with individual handling of each operation:

function divide(a, b) {
  if (b === 0) {
    throw new Error('Division by zero');
  }
  return a / b;
}
 
function calculate() {
  console.log('1. Start calculation');
  
  // Handle each operation separately
  try {
    const result1 = divide(10, 2);
    console.log('2. Result 1:', result1);
  } catch (error) {
    console.log('Error in operation 1:', error.message);
  }
  
  try {
    const result2 = divide(10, 0);
    console.log('3. Result 2:', result2);
  } catch (error) {
    console.log('Error in operation 2:', error.message);
  }
  
  try {
    const result3 = divide(20, 4);
    console.log('4. Result 3:', result3);
  } catch (error) {
    console.log('Error in operation 3:', error.message);
  }
  
  console.log('5. End calculation');
}
 
calculate();

This will output:

1. Start calculation
2. Result 1: 5
Error in operation 2: Division by zero
4. Result 3: 5
5. End calculation

It’s important to understand that when an exception occurs in a try block, execution immediately jumps to the catch block, and the rest of the code in the try block doesn’t execute.


Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪