TypeScript Error Handling

Managing errors and exceptions in TypeScript applications

🛡️ What is Error Handling?

Error handling helps your application gracefully manage unexpected situations and failures. TypeScript enhances error handling with type safety, custom error classes, and compile-time checks to catch potential issues early.


// Basic error handling
try {
    const result = riskyOperation();
    console.log(result);
} catch (error) {
    console.error("Something went wrong:", error);
}
                                    

Output:

Something went wrong: Error message

Error Handling Techniques

🎯

Try/Catch

Catch and handle runtime errors

try {
    // risky code
} catch (error) {
    // handle error
}
🏷️

Custom Errors

Create specific error types

class ValidationError 
    extends Error {
    constructor(msg: string) {
        super(msg);
    }
}
🔍

Type Guards

Check error types safely

if (error instanceof 
    ValidationError) {
    // handle validation
}

Result Types

Return success or error objects

type Result<T> = 
    | { ok: true; value: T }
    | { ok: false; error: string }

🔹 Try/Catch/Finally

The fundamental error handling structure:

function divideNumbers(a: number, b: number): number {
    try {
        if (b === 0) {
            throw new Error("Cannot divide by zero");
        }
        return a / b;
    } catch (error) {
        if (error instanceof Error) {
            console.error("Error:", error.message);
        }
        return 0;
    } finally {
        console.log("Division operation completed");
    }
}

console.log(divideNumbers(10, 2));  // 5
console.log(divideNumbers(10, 0));  // 0

Output:

Division operation completed
5
Error: Cannot divide by zero
Division operation completed
0

🔹 Custom Error Classes

Create specific error types for different scenarios:

// Base custom error
class AppError extends Error {
    constructor(
        public message: string,
        public statusCode: number
    ) {
        super(message);
        this.name = this.constructor.name;
    }
}

// Specific error types
class ValidationError extends AppError {
    constructor(message: string) {
        super(message, 400);
    }
}

class NotFoundError extends AppError {
    constructor(resource: string) {
        super(`${resource} not found`, 404);
    }
}

// Using custom errors
function findUser(id: number) {
    if (id < 0) {
        throw new ValidationError("User ID must be positive");
    }
    if (id > 1000) {
        throw new NotFoundError("User");
    }
    return { id, name: "John Doe" };
}

try {
    const user = findUser(-1);
} catch (error) {
    if (error instanceof ValidationError) {
        console.log("Validation failed:", error.message);
    } else if (error instanceof NotFoundError) {
        console.log("Not found:", error.message);
    }
}

Output:

Validation failed: User ID must be positive

🔹 Type-Safe Error Handling

Use TypeScript's type system for safer error handling:

// Unknown type for caught errors
function handleError(error: unknown) {
    if (error instanceof Error) {
        console.error("Error message:", error.message);
    } else if (typeof error === "string") {
        console.error("String error:", error);
    } else {
        console.error("Unknown error:", error);
    }
}

// Type guard for error checking
function isError(value: unknown): value is Error {
    return value instanceof Error;
}

// Using the type guard
try {
    throw new Error("Something failed");
} catch (error) {
    if (isError(error)) {
        console.log("Caught error:", error.message);
    }
}

Output:

Caught error: Something failed

🔹 Result Type Pattern

Return success or error without throwing exceptions:

// Define Result type
type Result<T, E = Error> = 
    | { success: true; value: T }
    | { success: false; error: E };

// Function using Result type
function parseNumber(input: string): Result<number, string> {
    const num = parseInt(input);
    
    if (isNaN(num)) {
        return {
            success: false,
            error: "Invalid number format"
        };
    }
    
    return {
        success: true,
        value: num
    };
}

// Using the Result type
const result1 = parseNumber("42");
if (result1.success) {
    console.log("Parsed:", result1.value);
} else {
    console.log("Error:", result1.error);
}

const result2 = parseNumber("abc");
if (result2.success) {
    console.log("Parsed:", result2.value);
} else {
    console.log("Error:", result2.error);
}

Output:

Parsed: 42
Error: Invalid number format

🔹 Async Error Handling

Handle errors in asynchronous code:

// Async function with error handling
async function fetchUserData(id: number): Promise<object> {
    try {
        // Simulate API call
        if (id <= 0) {
            throw new Error("Invalid user ID");
        }
        
        return {
            id,
            name: "Alice",
            email: "[email protected]"
        };
    } catch (error) {
        console.error("Failed to fetch user:", error);
        throw error; // Re-throw for caller to handle
    }
}

// Using async error handling
async function displayUser() {
    try {
        const user = await fetchUserData(1);
        console.log("User:", user);
        
        const invalidUser = await fetchUserData(-1);
    } catch (error) {
        if (error instanceof Error) {
            console.log("Caught:", error.message);
        }
    }
}

displayUser();

Output:

User: { id: 1, name: "Alice", email: "[email protected]" }
Failed to fetch user: Error: Invalid user ID
Caught: Invalid user ID

🔹 Best Practices

Follow these guidelines for effective error handling:

✅ Do:

  • Use specific error types for different scenarios
  • Always type catch blocks as unknown
  • Provide meaningful error messages for debugging
  • Log errors for monitoring and debugging
  • Clean up resources in finally blocks

❌ Don't:

  • Swallow errors without handling them
  • Use any type for error parameters
  • Throw non-Error objects (use Error class)
  • Ignore async errors in promises
// ❌ Bad: Swallowing errors
try {
    riskyOperation();
} catch (error) {
    // Silent failure - bad!
}

// ✅ Good: Proper error handling
try {
    riskyOperation();
} catch (error) {
    if (error instanceof Error) {
        console.error("Operation failed:", error.message);
        // Take appropriate action
    }
}

🧠 Test Your Knowledge

What type should you use for the error parameter in a catch block?