TypeScript Error Handling

Error handling is an important part of ensuring program robustness.

TypeScript provides a powerful type system that can help developers better handle and prevent errors.

This article introduces common error handling patterns and best practices in TypeScript.


SVG Diagram: Error Handling Approaches Background Title Error Handling Approaches Method 1: try-catch try-catch try { // code that may fail } catch (e) { // handle the error } Catch and handle exceptions Method 2: Result Type Result Type type Result = | { ok: true; value: T } | { ok: false; error: E } Return a result rather than throwing an exception Method 3: Error Types Custom errors class AppError extends Error { code: string; } Structured error information Bottom section Best Practices Practice 1 Specify error types clearly Practice 2 Centralized error handling Practice 3 Avoid exception leakage

Why Good Error Handling Is Needed

Any program may encounter error situations, such as network request failures, missing files, invalid user input, and so on.

Good error handling can prevent program crashes, provide friendly error messages, and help developers locate problems.

TypeScript's type system can find potential problems at compile time, reducing runtime errors.

Concept explanation:There are two main ways to handle errors: exception handling (try-catch) and return value handling (Result type). The former throws exceptions to indicate errors, while the latter carries error information through return values.


Custom Error Types

Create custom error types by extending the Error class, allowing them to carry more error information.

This makes error handling more precise and structured.

Example

// Define the application error class
// Extend the built-in Error class and add an error code
class AppError extends Error {
    // Error code for programmatic error handling
    code: string;

    // Constructor
    constructor(message: string, code: string) {
        super(message);  // Call the parent class constructor
        this.name = "AppError";  // Set the error name
        this.code = code;  // Save the error code
    }
}

// Safe division function
function divide(a: number, b: number): number {
    // Check if the divisor is zero
    if (b === 0) {
        // Throw a custom error
        throw new AppError("Cannot divide by zero", "DIVIDE_BY_ZERO");
    }
    return a / b;
}

// Use try-catch to catch the error
try {
    var result = divide(10, 0);
} catch (error) {
    // Check the error type
    if (error instanceof AppError) {
        console.log("Application error: " + error.message + ", code: " + error.code);
    } else {
        console.log("Unknown error: " + error);
    }
}

Output:

应用错误: Cannot divide by zero, 代码: DIVIDE_BY_ZERO

Error code:Adding codes to errors helps the program handle different types of errors more precisely.


Result Type to Avoid Exceptions

Another error handling approach is to use the Result type.

It carries error information through return values instead of throwing exceptions. This approach is common in functional programming.

Example

// Define a Result type using a union type
// On success includes ok: true and a value; on failure includes ok: false and an error
type Result<T, E = Error> =
    | { ok: true; value: T }
    | { ok: false; error: E };

// Division function using the Result type
function safeDivide(a: number, b: number): Result<number, string> {
    // Check if the divisor is zero
    if (b === 0) {
        // Return an error result
        return { ok: false, error: "Cannot divide by zero" };
    }
    // Return a success result
    return { ok: true, value: a / b };
}

// Call the function and handle the result
var result = safeDivide(10, 2);

// Handle based on the result type
if (result.ok) {
    console.log("Result: " + result.value);
} else {
    console.log("Error: " + result.error);
}

Output:

结果: 5

Advantages:The Result type makes error handling explicit; callers must handle possible errors and cannot ignore them.


Async Function Error Handling

In async functions, error handling is especially important.

You can use try-catch or the Result type to handle errors in asynchronous operations.

Example

// Define a user interface
interface User {
    id: number;
    name: string;
}

// Simulate an async function to fetch a user
async function fetchUser(id: number): Promise<Result<User, Error>> {
    try {
        // Simulate a network request
        var response = await fetch("/api/users/" + id);
        var user = await response.json();
        // Return a success result
        return { ok: true, value: user };
    } catch (error) {
        // Return an error result
        return { ok: false, error: error as Error };
    }
}

// Main function
async function main() {
    // Call the async function
    var result = await fetchUser(1);

    // Handle the result
    if (result.ok) {
        console.log("User: " + JSON.stringify(result.value));
    } else {
        console.log("Error: " + result.error.message);
    }
}

// Execute the main function
main();

Output:

User: {"id":1,"name":"Alice"}

Tip:The try-catch in an async function catches any error thrown by await expressions.


General Error Handling Wrapper

You can create a general error handling function to simplify error handling in asynchronous code.

Example

// General error handling wrapper function
// Accept an async function and return a Result type
async function withErrorHandling<T>(
    fn: () => Promise<T>
): Promise<Result<T, Error>> {
    try {
        // Execute the passed-in async function
        var data = await fn();
        // Return a success result
        return { ok: true, value: data };
    } catch (error) {
        // Return an error result
        return { ok: false, error: error as Error };
    }
}

// Use general error handling
// Simulate fetching data
var result = await withErrorHandling(async function() {
    var response = await fetch("/api/data");
    return response.json();
});

// Handle based on the result
if (result.ok) {
    console.log("Data: " + JSON.stringify(result.value));
} else {
    console.error("Error:", result.error);
}

Best practice:Wrapping general error handling logic reduces code duplication and improves code maintainability.


Notes

  • Don't ignore errors:Do not use empty catch blocks to catch and ignore errors
  • Specify error types clearly:Use specific error types whenever possible, rather than the generic Error
  • Error boundaries:Establish a unified error handling mechanism in the application
  • Don't overuse exceptions:For predictable errors, prefer return values over throwing exceptions

Suggestion:Choose an error handling approach based on the scenario: use exceptions for program errors, and Result for business errors.


Summary

Good error handling is the foundation for building robust applications.

  • Custom errors:Extend the Error class and add information such as error codes
  • Result type:Handle errors through return values, avoiding exceptions
  • async/await:Use try-catch for asynchronous errors
  • Error wrapping:Create general error handling functions
  • Error boundaries:Establish a unified error handling mechanism

Best practice:Choose an appropriate error handling approach based on the specific scenario, balancing code readability and robustness.


Other Extensions