Skip to content
intermediatePhase 33 · Advanced JavaScript

Error Handling

Use try/catch/finally, custom errors, and error propagation strategies.

30m
0 problems
Topic Progress0%

try/catch/finally

The try/catch/finally statement handles exceptions and cleanup operations.

Basic Syntax

try {
  // Code that might throw an error
  const result = riskyOperation();
} catch (error) {
  // Handle the error
  console.error('Error:', error.message);
} finally {
  // Always runs, regardless of success or failure
  cleanup();
}

Without catch or finally

// try with finally (no catch)
try {
  doSomething();
} finally {
  cleanup(); // Runs even if doSomething throws
}

// try with catch (no finally)
try {
  doSomething();
} catch (error) {
  handleError(error);
}

Error Object Properties

try {
  riskyOperation();
} catch (error) {
  console.log(error.name);     // 'TypeError', 'ReferenceError', etc.
  console.log(error.message);  // Human-readable description
  console.log(error.stack);    // Stack trace
}

Nested try/catch

try {
  try {
    operation1();
  } catch (error) {
    console.log('Operation 1 failed:', error);
    operation2();
  }
} catch (error) {
  console.log('Operation 2 failed:', error);
}

Custom Errors

You can create custom error types for better error handling and debugging.

Creating Custom Errors

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

// Usage
function validateAge(age) {
  if (age < 0 || age > 150) {
    throw new ValidationError('Invalid age', 'age');
  }
  return age;
}

try {
  validateAge(200);
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(`${error.field}: ${error.message}`);
  }
}

Common Custom Errors

// API Error
class ApiError extends Error {
  constructor(message, statusCode, data) {
    super(message);
    this.name = 'ApiError';
    this.statusCode = statusCode;
    this.data = data;
  }
}

// Not Found Error
class NotFoundError extends Error {
  constructor(resource, id) {
    super(`${resource} with id ${id} not found`);
    this.name = 'NotFoundError';
    this.resource = resource;
    this.id = id;
  }
}

// Authentication Error
class AuthError extends Error {
  constructor(message = 'Authentication required') {
    super(message);
    this.name = 'AuthError';
  }
}

Error Handling by Type

try {
  // Some operation
} catch (error) {
  if (error instanceof ValidationError) {
    // Handle validation
  } else if (error instanceof ApiError) {
    // Handle API error
  } else if (error instanceof TypeError) {
    // Handle type error
  } else {
    // Handle unknown errors
    console.error('Unexpected error:', error);
  }
}

Error Propagation

Errors propagate up the call stack until caught. Understanding this helps write robust code.

How Errors Propagate

function outer() {
  inner(); // Error from inner propagates here
}

function inner() {
  middle(); // Error from middle propagates here
}

function middle() {
  throw new Error('Something went wrong');
}

try {
  outer(); // Caught here
} catch (error) {
  console.error(error.message); // 'Something went wrong'
}

Re-throwing Errors

function processData(data) {
  try {
    validateData(data);
    return transform(data);
  } catch (error) {
    console.log('Processing failed:', error.message);
    throw error; // Re-throw for caller to handle
  }
}

Error Handling in Async Code

// Promise chain
fetchData()
  .then(process)
  .catch(handleError); // Catches errors from any step

// Async/await
async function main() {
  try {
    const data = await fetchData();
    const result = await process(data);
    return result;
  } catch (error) {
    handleError(error);
  }
}

Best Practices

// 1. Always provide context
throw new Error(`Failed to process user ${userId}: ${originalError.message}`);

// 2. Use specific error types
throw new ValidationError('Email is required', 'email');

// 3. Don't swallow errors silently
try {
  riskyOperation();
} catch (error) {
  console.error(error); // At minimum, log it
}

// 4. Clean up in finally
try {
  const resource = acquireResource();
  useResource(resource);
} finally {
  releaseResource(); // Always cleanup
}

Practice Problems

0/3solved
Build Error Handling Component

Create a reusable React component implementing Error Handling. Include proper state management and accessibility.

Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where needed
Error Handling Testing

Write unit and integration tests for Error Handling using React Testing Library.

Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility tests
Error Handling Performance

Optimize Error Handling for performance. Consider memoization, code splitting, and bundle size.

Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysis

Quiz

1. When does the finally block execute?

Question 1 options

2. What is the benefit of custom errors?

Question 2 options

3. What happens if no catch block handles an error?

Question 3 options

4. How do you re-throw an error?

Question 4 options

Flashcards

Question

What is try/catch/finally?

Answer

Statements for handling exceptions. try wraps risky code, catch handles errors, finally runs cleanup regardless of outcome.

Question

What is error propagation?

Answer

Errors travel up the call stack until caught by a catch block. If uncaught, they become uncaught exceptions.

Question

How to create a custom error?

Answer

Extend the Error class: class MyError extends Error { constructor(msg) { super(msg); this.name = 'MyError'; } }

Question

What does the error object contain?

Answer

name (error type), message (description), and stack (stack trace showing where error occurred).

Question

What is Error Handling?

Answer

Error Handling is a key concept in frontend development.

Revision Notes

Key Takeaways

  • 1.try/catch/finally handles errors and cleanup
  • 2.Errors propagate up the call stack until caught
  • 3.Custom errors improve debugging and handling
  • 4.finally blocks always execute, regardless of errors
  • 5.Always provide context when throwing errors

Interview Tips

  • Explain when to use try/catch vs .catch()
  • Discuss error propagation with async code
  • Show how to create meaningful custom error types
  • Explain the importance of cleanup in finally blocks

Cheat Sheet

Error Handling Cheat Sheet

Basic Syntax

try {
  riskyOperation();
} catch (error) {
  handleError(error);
} finally {
  cleanup();
}

Custom Errors

class MyError extends Error {
  constructor(msg) {
    super(msg);
    this.name = 'MyError';
  }
}

Error Object

  • name: Error type
  • message: Description
  • stack: Stack trace

Best Practices

  • Always provide context in error messages
  • Use specific error types
  • Clean up in finally blocks
  • Don't swallow errors silently