Error Handling in TypeScript: Advanced try/catch Techniques

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 Basic try/catch to Industrial Patterns

\n\n

Error handling is not just about try/catch blocks. In TypeScript, thanks to its strict type system, we can take error management to a fundamentally new level. In this article, we will explore advanced techniques that will help you write more reliable and predictable code.

\n\n

The Problem with Standard try/catch in TypeScript

\n\n

The classic try/catch block in TypeScript has a fundamental problem: the type of the caught error is unknown (or any in older versions). This means TypeScript doesn't know what kind of object we are dealing with.

\n\n
try {\n  // some dangerous code\n  const result = JSON.parse('{"data": 123}');\n} catch (error) {\n  // error has type unknown\n  // Cannot simply call error.message\n  console.log(error.message); // Compilation error!\n}
\n\n

This forces developers to either ignore types or write cumbersome checks. Let's look at how to solve this problem at an advanced level.

\n\n

1. Creating a System of Typed Custom Errors

\n\n

Instead of throwing plain Error objects, create a hierarchy of custom errors. This allows TypeScript to distinguish between error types at compile time.

\n\n
// Base class for all application errors\nclass AppError extends Error {\n  constructor(\n    message: string,\n    public readonly code: string,\n    public readonly statusCode: number = 500\n  ) {\n    super(message);\n    this.name = this.constructor.name;\n  }\n}\n\n// Specific error types\nclass ValidationError extends AppError {\n  constructor(message: string) {\n    super(message, 'VALIDATION_ERROR', 400);\n  }\n}\n\nclass NotFoundError extends AppError {\n  constructor(resource: string) {\n    super(`${resource} not found`, 'NOT_FOUND', 404);\n  }\n}\n\nclass DatabaseError extends AppError {\n  constructor(message: string) {\n    super(message, 'DB_ERROR', 500);\n  }\n}\n\n// Usage\nfunction findUser(id: string): User {\n  if (!id) throw new ValidationError('ID is required');\n  // ... search logic\n  if (!user) throw new NotFoundError('User');\n  return user;\n}
\n\n

2. Type Guards for Error Handling

\n\n

To safely handle different error types, use type guards. This is a key pattern for working with unknown in catch.

\n\n
// Type guard for AppError\nfunction isAppError(error: unknown): error is AppError {\n  return error instanceof AppError;\n}\n\n// Type guard for ValidationError\nfunction isValidationError(error: unknown): error is ValidationError {\n  return error instanceof ValidationError;\n}\n\n// Advanced handling\ntry {\n  const user = findUser(userId);\n} catch (error) {\n  if (isValidationError(error)) {\n    // TypeScript knows error is ValidationError\n    console.error(`Validation error: ${error.message}`);\n    return { status: 400, message: error.message };\n  }\n  \n  if (isAppError(error)) {\n    // error is AppError\n    console.error(`Error code: ${error.code}`);\n    return { status: error.statusCode, message: error.message };\n  }\n  \n  // Unknown error\n  if (error instanceof Error) {\n    console.error('Unknown error:', error.message);\n  }\n  \n  return { status: 500, message: 'Internal server error' };\n}
\n\n

3. Result Pattern (Either monad)

\n\n

This is an alternative to exceptions that makes error handling explicit at the type level. Instead of throwing an error, the function returns an object that can contain either a successful result or an error.

\n\n
// Type definitions for the Result pattern\ntype Success<T> = { success: true; data: T };\ntype Failure<E> = { success: false; error: E };\ntype Result<T, E> = Success<T> | Failure<E>;\n\n// Helper functions\nfunction success<T>(data:                

Blogs

Book Recommendations