YouTip LogoYouTip

Ts Error Handling

TypeScript Error Handling |

TypeScript Error Handling

Error handling is an important part of ensuring program robustness.

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

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


Why Good Error Handling Is Needed

Any program may encounter error situations such as network request failures, missing files, or incorrect user input.

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

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

Concept Explanation: There are two main approaches to error handling: exception handling (try-catch) and return value handling (Result type). The former uses throwing exceptions to indicate errors, while the latter uses return values to carry error information.


Custom Error Types

Creating custom error types by extending the Error class allows carrying more error information.

This makes error handling more precise and structured.

Example

// Define application error class
// Extend built-in Error class, add error code
class AppError extends Error {
    // Error code for programmatic error handling
    code: string;
    
    // Constructor
    constructor(message: string, code: string){
        super(message); // Call parent constructor
        this.name = "AppError"; // Set error name
        this.code = code; // Save error code
    }
}

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

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

Output:

Application error: Cannot divide by zero, code: DIVIDE_BY_ZERO

Error Codes: Adding codes to errors helps programs handle different types of errors more precisely.


Using Result Type to Avoid Exceptions

Another approach to error handling is using the Result type.

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

Example

// Define Result type using union types
// Success case includes ok: true and value, failure case includes ok: false and error
type Result =
    | { ok: true; value: T }
    | { ok: false; error: E };

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

// Call function and process result
var result = safeDivide(10, 2);
// Process based on result type
if(result.ok){
    console.log("Result: "+ result.value);
}else{
    console.log("Error: "+ result.error);
}

Output:

Result: 5

Advantages: The Result type makes error handling explicit. Callers must handle possible errors instead of ignoring them.


Async Function Error Handling

In asynchronous functions, error handling is especially important.

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

Example

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

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

// Main function
async function main(){
    // Call async function
    var result = await fetchUser(1);
    // Process result
    if(result.ok){
        console.log("User: "+ JSON.stringify(result.value));
    }else{
        console.log("Error: "+ result.error.message);
    }
}

// Execute main function
main();

Output:

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

Tip: In async functions, try-catch catches any errors thrown by await expressions.


General Error Handling Encapsulation

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

Example

// General error handling wrapper function
// Accepts an async function and returns a Result type
async function withErrorHandling(
    fn: () => Promise
): Promise<Result>{
    try{
        // Execute the passed async function
        var data = await fn();
        // Return success result
        return { ok: true, value: data };
    }catch(error){
        // Return 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();
});

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

Best Practice: Encapsulating general error handling logic reduces code duplication and improves maintainability.


Considerations

  • Don't ignore errors: Don't use empty catch blocks to catch and ignore errors
  • Specify error types clearly: Prefer specific error types over generic Error
  • Error boundaries: Establish unified error handling mechanisms in your application
  • Avoid overusing exceptions: For predictable errors, prefer return values over throwing exceptions

Suggestion: Choose error handling methods based on context: use exceptions for program errors, Result for business errors.


Summary

Good error handling is the foundation of building robust applications.

  • Custom Errors: Extend Error class, add error codes and other information
  • Result Type: Handle errors via return values, avoid exceptions
  • async/await: Use try-catch to handle asynchronous errors
  • Error Encapsulation: Create general error handling functions
  • Error Boundaries: Establish unified error handling mechanisms

Best Practice: Choose appropriate error handling methods based on specific scenarios, balancing code readability and robustness.

← Ts Template LiteralTs Mapped Types β†’