Advanced Error Handling Techniques in TypeScript

Effective error handling is crucial for building robust TypeScript applications. Beyond basic try-catch blocks, TypeScript provides several advanced techniques to handle errors gracefully and ensure code reliability. This article explores some of these advanced error handling strategies.

1. Custom Error Classes

Creating custom error classes allows you to represent different types of errors more accurately. Custom errors can include additional properties or methods, which can help in identifying and handling specific issues.

class CustomError extends Error {
  constructor(public message: string, public code: number) {
    super(message);
    this.name = 'CustomError';
  }
}

function throwError() {
  throw new CustomError('Something went wrong', 500);
}

try {
  throwError();
} catch (error) {
  if (error instanceof CustomError) {
    console.error(`Error: ${error.message}, Code: ${error.code}`);
  } else {
    console.error('Unexpected error:', error);
  }
}

In this example, CustomError extends the built-in Error class and adds a code property to specify an error code.

2. Error Handling in Asynchronous Code

Asynchronous code often requires special handling for errors. Using async and await along with try-catch blocks can simplify error handling in asynchronous operations.

async function fetchData(url: string): Promise {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new CustomError('Failed to fetch data', response.status);
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    if (error instanceof CustomError) {
      console.error(`Error: ${error.message}, Code: ${error.code}`);
    } else {
      console.error('Unexpected error:', error);
    }
  }
}

fetchData('https://api.example.com/data');

This example demonstrates handling errors from an asynchronous fetch call using async, await, and try-catch.

3. Error Boundaries in React with TypeScript

When working with React and TypeScript, error boundaries help catch errors in the component tree and display a fallback UI. Implementing error boundaries with TypeScript ensures type safety and proper error handling.

import React, { Component, ErrorInfo } from 'react';

interface Props {}

interface State {
  hasError: boolean;
}

class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

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

  componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
    console.error('Error caught by boundary:', error, errorInfo);
  }

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

    return this.props.children;
  }
}

export default ErrorBoundary;

In this React example, the ErrorBoundary component catches errors in its child components and displays a fallback UI if an error occurs.

4. Using Type Guards for Error Types

Type guards help narrow down the type of an error in TypeScript. This is particularly useful when handling errors with different types or from various sources.

function isCustomError(error: any): error is CustomError {
  return error instanceof CustomError;
}

try {
  throw new CustomError('Example error', 400);
} catch (error) {
  if (isCustomError(error)) {
    console.error(`CustomError: ${error.message}, Code: ${error.code}`);
  } else {
    console.error('Unknown error:', error);
  }
}

The isCustomError function is a type guard that helps determine if the caught error is an instance of CustomError.

5. Centralized Error Handling

For large applications, centralizing error handling can simplify error management and ensure consistency. This can be done using middleware in Express.js or global error handlers in other frameworks.

import express, { Request, Response, NextFunction } from 'express';

const app = express();

app.use((err: any, req: Request, res: Response, next: NextFunction) => {
  console.error('Centralized Error:', err.message);
  res.status(500).send('Internal Server Error');
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

This example demonstrates a centralized error handler for an Express.js application. It catches all errors and responds with a generic message.

Conclusion

Advanced error handling techniques in TypeScript enhance the robustness of your applications by providing more control over error management. Custom error classes, handling asynchronous errors, using error boundaries in React, type guards, and centralized error handling are essential strategies for effective error management. Implementing these techniques will lead to more maintainable and reliable code.