Mastering Type Guards: A Practical Approach to Handling Errors in TypeScript

Learn how to use TypeScript type guards to effectively handle errors and improve your code's safety and readability with practical examples.

TypeScript helps developers catch errors early by providing strong typing. However, when dealing with errors or unknown values at runtime, TypeScript needs extra hints to understand what type a variable really has. This is where type guards come in. Type guards are functions or conditions that allow TypeScript to narrow down the type of a variable within a conditional block, making error handling more robust and readable.

Let's explore how to use type guards to handle errors effectively in TypeScript.

First, consider an example where you might receive an unknown value that could be an error or some other type. You want to check if this value is an instance of the built-in Error class before accessing its message property.

typescript
function isError(value: unknown): value is Error {
  return value instanceof Error;
}

function processValue(value: unknown) {
  if (isError(value)) {
    console.log('Error message:', value.message);
  } else {
    console.log('Value is not an error:', value);
  }
}

// Usage examples
processValue(new Error('Something went wrong'));
processValue('Just a string');

In the code above, `isError` is a user-defined type guard. The special return type `value is Error` tells TypeScript that inside the `if` block, `value` should be treated as an `Error` instance. This allows you to safely access `value.message` without risking a runtime error or TypeScript type error.

Type guards are very useful when working with custom error types. For example, you might have the following custom error class:

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

function isValidationError(error: unknown): error is ValidationError {
  return error instanceof ValidationError;
}

function handleError(error: unknown) {
  if (isValidationError(error)) {
    console.error(`Validation error in field: ${error.field}`);
  } else if (isError(error)) {
    console.error('General error:', error.message);
  } else {
    console.error('Unknown error:', error);
  }
}

handleError(new ValidationError('email', 'Invalid email address'));
handleError(new Error('Unexpected failure'));
handleError('Unknown failure');

This approach improves both type safety and clarity in your error handling code. Using type guards helps TypeScript understand exactly what type it’s working with, preventing mistakes and making your code easier to read and maintain.

In summary, mastering type guards in TypeScript allows you to:

- Safely check unknown or union types. - Narrow types to specific error instances. - Write cleaner, safer error handling logic. Practice writing custom type guards along with built-in checks like `instanceof` and `typeof` to become confident in managing errors the TypeScript way.