TypeScript Error Handling Beginners Guide

Effective error handling is crucial in any programming language, and TypeScript is no exception. Proper error handling helps in creating robust and reliable applications by managing unexpected conditions gracefully. This guide will cover the basics of error handling in TypeScript and provide practical examples for beginners.

Understanding Errors in TypeScript

Errors in TypeScript, like in JavaScript, occur during runtime or compile-time. TypeScript provides type safety that can catch many potential issues at compile time, but runtime errors still need to be managed properly.

Basic Error Handling with try and catch

In TypeScript, you handle runtime errors using the try and catch blocks. This approach allows you to execute code that might throw an error and handle that error if it occurs.

Example of try and catch

function divide(a: number, b: number): number {
  try {
    if (b === 0) {
      throw new Error("Cannot divide by zero");
    }
    return a / b;
  } catch (error) {
    console.error(error.message);
    return NaN; // Return NaN to indicate an error
  }
}

console.log(divide(10, 2)); // Output: 5
console.log(divide(10, 0)); // Output: Cannot divide by zero

In this example, the divide function attempts to divide two numbers. If the divisor is zero, an error is thrown and caught by the catch block, which logs an error message.

Custom Error Types

TypeScript allows you to define custom error types to better represent specific error conditions. Custom error types help in categorizing errors and handling them more effectively.

Creating a Custom Error Type

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

function divide(a: number, b: number): number {
  try {
    if (b === 0) {
      throw new DivisionError("Cannot divide by zero");
    }
    return a / b;
  } catch (error) {
    if (error instanceof DivisionError) {
      console.error(`Custom Error: ${error.message}`);
    } else {
      console.error("An unexpected error occurred");
    }
    return NaN; // Return NaN to indicate an error
  }
}

console.log(divide(10, 2)); // Output: 5
console.log(divide(10, 0)); // Output: Custom Error: Cannot divide by zero

Here, we define a custom error class DivisionError that extends the built-in Error class. We use this custom error in the divide function to provide more specific error handling.

Type Guarding with instanceof

Type guards like instanceof help narrow down the type of an error object in the catch block, allowing you to handle different error types differently.

Example of Type Guarding

function processInput(input: string | number) {
  try {
    if (typeof input === "string") {
      console.log(input.toUpperCase());
    } else {
      throw new Error("Input must be a string");
    }
  } catch (error) {
    if (error instanceof Error) {
      console.error(`Error: ${error.message}`);
    } else {
      console.error("An unknown error occurred");
    }
  }
}

processInput("hello"); // Output: HELLO
processInput(42); // Output: Error: Input must be a string

This example demonstrates type guarding in the catch block to ensure the error object is an instance of Error, allowing for accurate error handling.

Using finally for Cleanup

The finally block can be used to execute code that should run regardless of whether an error occurred or not. This is useful for cleanup operations such as closing files or releasing resources.

Example with finally

function readFile(filePath: string): string {
  try {
    // Simulate reading a file
    if (filePath === "") {
      throw new Error("File path cannot be empty");
    }
    return "File content";
  } catch (error) {
    console.error(`Error: ${error.message}`);
    return "";
  } finally {
    console.log("Cleanup: Closing file");
  }
}

console.log(readFile("path/to/file")); // Output: File content
console.log(readFile("")); // Output: Error: File path cannot be empty
                            //         Cleanup: Closing file

In this example, the finally block ensures that a cleanup message is logged regardless of whether an error occurs.

Conclusion

Effective error handling is crucial for building reliable TypeScript applications. By using try and catch, custom error types, type guarding, and finally, you can manage errors more effectively and ensure that your application behaves predictably even in the face of unexpected conditions.

With these techniques, you can handle errors gracefully and improve the robustness of your TypeScript code. Practice these concepts to become proficient in TypeScript error handling and write more resilient applications.