TypeScript Error Handling

Error handling is a crucial aspect of any programming language, and TypeScript is no exception. Proper error handling allows developers to manage and respond to runtime errors effectively, ensuring that applications remain robust and reliable. In this chapter, we will explore error handling in TypeScript from basic to advanced techniques, providing detailed examples and explanations. Our goal is to cover all aspects of TypeScript error handling, ensuring a comprehensive understanding of the topic.

Basics of Error Handling

Understanding Errors

Errors in programming can be broadly categorized into two types:

  • Compile-time Errors: Detected by the TypeScript compiler.
  • Runtime Errors: Occur during the execution of the program.

TypeScript’s strong typing system helps catch many errors at compile time, but runtime errors still need to be managed using proper error handling techniques.

The try...catch Statement

The try...catch statement allows you to catch and handle exceptions that occur during the execution of a block of code.

Example

				
					// Base class
class Person {
    constructor(public name: string) {}
}

// Mixin function
function CanEat<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        eat() {
            console.log(`${this.name} is eating.`);
        }
    };
}

// Helper type
type Constructor = new (...args: any[]) => {};

// Using the mixin
class Student extends CanEat(Person) {}

const student = new Student('John');
student.eat(); // Output: John is eating.
				
			

Explanation:

  • The try block contains code that might throw an error.
  • The catch block handles any errors that occur in the try block.
  • The error parameter contains information about the thrown error.

Output:

				
					An error occurred: [Error details]
				
			

Throwing Errors

Throwing Custom Errors

You can throw custom errors using the throw statement. This is useful for creating specific error types for different error conditions.

Example

				
					function validateInput(input: string) {
    if (input.trim() === "") {
        throw new Error("Input cannot be empty");
    }
    return input;
}

try {
    validateInput("");
} catch (error) {
    console.error(error.message); // Output: Input cannot be empty
}
				
			

Explanation:

  • The validateInput function throws an error if the input is an empty string.
  • The catch block handles the error and logs the error message.

Output:

				
					Input cannot be empty
				
			

Creating Custom Error Classes

Custom Error Classes

Creating custom error classes helps in defining specific error types and handling them appropriately.

Example

				
					class ValidationError extends Error {
    constructor(message: string) {
        super(message);
        this.name = "ValidationError";
    }
}

function validateAge(age: number) {
    if (age < 0 || age > 120) {
        throw new ValidationError("Age must be between 0 and 120");
    }
    return age;
}

try {
    validateAge(-5);
} catch (error) {
    if (error instanceof ValidationError) {
        console.error("Validation error:", error.message);
    } else {
        console.error("Unknown error:", error);
    }
}
				
			

Explanation:

  • ValidationError is a custom error class that extends the built-in Error class.
  • The validateAge function throws a ValidationError if the age is not between 0 and 120.
  • The catch block checks if the error is an instance of ValidationError and handles it accordingly.

Output:

				
					Validation error: Age must be between 0 and 120
				
			

Advanced Error Handling Techniques

Asynchronous Error Handling

Handling errors in asynchronous code requires different techniques, especially when using async and await.

Example

				
					async function fetchData(url: string) {
    try {
        let response = await fetch(url);
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        let data = await response.json();
        return data;
    } catch (error) {
        console.error("Failed to fetch data:", error);
    }
}

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

Explanation:

  • The fetchData function uses async and await to handle asynchronous operations.
  • Errors are caught in the try...catch block and handled appropriately.

Output:

				
					Failed to fetch data: [Error details]
				
			

Using Promise.catch

For handling errors in promises, you can use the catch method.

Example

				
					function getData(url: string): Promise<any> {
    return fetch(url)
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
        })
        .catch(error => {
            console.error("Error fetching data:", error);
        });
}

getData("https://api.example.com/data");
				
			

Explanation:

  • The getData function returns a promise.
  • Errors are caught using the catch method of the promise.

Output:

				
					Error fetching data: [Error details]
				
			

Best Practices for Error Handling

Centralized Error Handling

Centralized error handling involves creating a single place to handle all errors, making it easier to manage and log errors consistently.

Example

				
					function handleError(error: Error) {
    // Log the error to an external service
    console.error("Logging error:", error);
}

try {
    throw new Error("Something went wrong");
} catch (error) {
    handleError(error);
}
				
			

Explanation:

  • The handleError function centralizes error logging.
  • All errors are handled through the handleError function, ensuring consistent error management.

Output:

				
					Logging error: Error: Something went wrong
				
			

Using Error Boundaries in React

When working with React, you can use error boundaries to catch errors in the component tree.

Example

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

class ErrorBoundary extends Component {
    state = { hasError: false };

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

    componentDidCatch(error: Error, errorInfo: ErrorInfo) {
        console.error("Error caught in boundary:", error, errorInfo);
    }

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

export default ErrorBoundary;
				
			

Explanation:

  • ErrorBoundary is a React component that catches errors in its child components.
  • Errors are logged and a fallback UI is displayed when an error occurs.

Output:

				
					Error caught in boundary: [Error details]
				
			

We covered TypeScript error handling in depth, from basic try...catch statements to creating custom error classes and handling asynchronous errors. We also explored best practices, such as centralized error handling and using error boundaries in React. Happy coding !❤️

Table of Contents