Error Handling in JavaScript

Built-in Error Types

JavaScript provides several built-in error types:

  • Error: Base error type
  • SyntaxError: Invalid syntax
  • ReferenceError: Reference to an undefined variable
  • TypeError: Operation on an inappropriate type
  • RangeError: Numeric value outside of valid range
  • URIError: Incorrect use of URI functions
  • EvalError: Error in the eval() function

Try-Catch-Finally

The primary mechanism for handling errors in JavaScript:

try {
  // Code that might throw an error
  const result = riskyOperation();
} catch (error) {
  // Handle the error
  console.error('An error occurred:', error.message);
} finally {
  // Code that always runs, regardless of error
  cleanupResources();
}

Creating Custom Errors

Extend the Error class to create custom error types:

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

try {
  if (!isValid(data)) {
    throw new ValidationError('Invalid data provided');
  }
} catch (error) {
  if (error instanceof ValidationError) {
    // Handle validation errors
  } else {
    // Handle other errors
  }
}

Error Properties

Common properties available on error objects:

try {
  throw new Error('Something went wrong');
} catch (error) {
  console.log(error.name);     // "Error"
  console.log(error.message);  // "Something went wrong"
  console.log(error.stack);    // Stack trace
}

Async Error Handling

With Promises

fetchData()
  .then(data => {
    // Process data
  })
  .catch(error => {
    // Handle errors from fetchData or any previous .then
    console.error('Error:', error.message);
  })
  .finally(() => {
    // Cleanup code
  });

With Async/Await

async function getData() {
  try {
    const data = await fetchData();
    return processData(data);
  } catch (error) {
    console.error('Error:', error.message);
    // Handle error or rethrow
    throw error;
  } finally {
    // Cleanup code
  }
}

Global Error Handling

In Browser

// Catch unhandled errors
window.addEventListener('error', (event) => {
  console.error('Unhandled error:', event.error);
  // Prevent default browser error handling
  event.preventDefault();
});

// Catch unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);
  // Prevent default handling
  event.preventDefault();
});

In Node.js

process.on('uncaughtException', (error) => {
  console.error('Uncaught exception:', error);
  // Perform cleanup
  process.exit(1); // Exit with failure code
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled promise rejection:', reason);
  // Handle the error or exit
});

Error Handling Patterns

Error Boundaries (React)

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    logErrorToService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

// Usage
<ErrorBoundary>
  <MyComponent />
</ErrorBoundary>

Retry Pattern

async function fetchWithRetry(url, options = {}, retries = 3, backoff = 300) {
  try {
    return await fetch(url, options);
  } catch (error) {
    if (retries <= 0) {
      throw error;
    }
    
    await new Promise(resolve => setTimeout(resolve, backoff));
    return fetchWithRetry(url, options, retries - 1, backoff * 2);
  }
}

Error Middleware (Express.js)

app.get('/api/data', (req, res, next) => {
  try {
    // Operation that might fail
    if (!req.user) {
      throw new Error('User not authenticated');
    }
    res.json({ data: 'success' });
  } catch (error) {
    next(error); // Pass to error middleware
  }
});

// Error middleware
app.use((error, req, res, next) => {
  console.error(error);
  res.status(500).json({
    error: {
      message: error.message || 'Internal Server Error'
    }
  });
});

Best Practices

  1. Be specific: Catch only the errors you can handle
  2. Don’t swallow errors: Always log or handle errors appropriately
  3. Use finally for cleanup: Ensure resources are released
  4. Centralize error handling: Use global handlers or middleware
  5. Include context: Add relevant information to error messages
  6. Fail fast: Detect and report errors as early as possible
  7. Validate inputs: Prevent errors by validating data before processing

Interview Tips

  • Explain the difference between throwing and handling errors
  • Describe how error propagation works in asynchronous code
  • Discuss strategies for debugging errors in production
  • Demonstrate knowledge of error handling patterns in frameworks
  • Explain how to create and use custom error types effectively

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.

Test Your JavaScript Knowledge

Ready to put your skills to the test? Take our interactive JavaScript quiz and get instant feedback on your answers.