Error Handling in TypeScript: try/catch and Advanced Methods

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Error Handling in TypeScript: from try/catch to Reliable Code



TypeScript adds static typing to JavaScript, but runtime errors remain an integral part of development. Improper exception handling can lead to application crashes, data leaks, or incorrect user interface behavior. In this article, we will explore how to effectively handle errors in TypeScript using built-in mechanisms and advanced patterns.



Basic try/catch in TypeScript



The try/catch construct is the foundation of error handling. In TypeScript, unlike JavaScript, we can type the caught error. However, there is an important nuance: by default, the error type in the catch block is unknown, not Error.



try {    const data = JSON.parse('invalid json');    console.log(data);} catch (error) {    // error has type unknown    if (error instanceof Error) {        console.error('An error occurred:', error.message);    } else {        console.error('Unknown error:', error);    }}


Why is this important? In JavaScript, you can throw anything: a string, a number, an object. TypeScript protects us from unsafe access to error properties without checking. It is recommended to always check the error type before using it.



Custom Error Classes



For more precise handling, create your own error classes extending the built-in Error. This allows you to distinguish between error types and pass additional information.



class ValidationError extends Error {    constructor(        public field: string,        message: string    ) {        super(message);        this.name = 'ValidationError';    }}

class NetworkError extends Error { constructor( public statusCode: number, message: string ) { super(message); this.name = 'NetworkError'; }}

function processUserData(data: unknown): void { if (!data || typeof data !== 'object') { throw new ValidationError('data', 'Invalid data'); } if (!('email' in data)) { throw new ValidationError('email', 'Email is required'); }}

try { processUserData({ name: 'Ivan' });} catch (error) { if (error instanceof ValidationError) { console.error(`Validation error in field ${error.field}: ${error.message}`); } else if (error instanceof NetworkError) { console.error(`Network error ${error.statusCode}: ${error.message}`); } else { console.error('Unknown error:', error); }}


This approach makes the code self-documenting and simplifies debugging. You always know where and why an error occurred.



Typing Functions with Errors: never and Discriminated Unions



TypeScript provides powerful tools for working with errors at the type level. Use the never type for functions that always throw an exception, and discriminated unions for returning errors.



function throwError(message: string): never {    throw new Error(message);}

// Discriminated union for the resulttype Result<T> = | { success: true; data: T } | { success: false; error: string };

function fetchUser(id: number): Result<{ name: string }> { try { // Simulating a request if (id <= 0) { return { success: false, error: 'Invalid ID' }; } return { success: true, data: { name: 'Anna' } }; } catch (error) { return { success: false, error: 'Network error' }; }}

const result = fetchUser(1);if (result.success) { console.log('User:', result.data.name);} else { console.error('Error:', result.error);}


The Result pattern allows you to avoid implicit exceptions and makes error handling

Blogs

Book Recommendations