bugl
bugl
HomeLearnPatternsPathsSearch
HomeLearnPatternsPathsSearch

Loading lesson path

Learn/TypeScript/TypeScript Core
TypeScript•TypeScript Core

TypeScript Error Handling

Flash cards

Review the key moves

1/4
Core idea

What is the main idea behind TypeScript Error Handling?

Lesson checks

Practice each idea before moving on

Short Mimo-style checks built from this lesson's code, terms, and sequence.

1Quick choice

Which statement best captures the main point of this lesson?

2Fill blank

Complete the missing token from the example code.

___ divide(a: number, b: number): number {
3Order

Put the learning moves in the order that makes the concept easiest to apply.

Custom Error Classes
Basic Error Handling
TypeScript Error Handling

Robust error handling is crucial for building reliable TypeScript applications.

This guide covers everything from basic try/catch to advanced error handling patterns.

Basic Error Handling

Try/Catch Blocks

function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error('Division by zero');
  }
return a / b;
}
try {
  const result = divide(10, 0);
  console.log(result);
} catch (error) {
console.error('An error occurred:', error.message);
}

In TypeScript 4.0 and later, the unknown type is the default type for catch variables. Always narrow the type before accessing properties.

Custom Error Classes

Error

Type Guards for Errors

Type Predicates for Error Handling

// Type guards function isErrorWithMessage(error: unknown): error is { message: string } {
return ( typeof error === 'object' && error !== null && 'message' in error && typeof (error as Record ).message === 'string' );
}
function isValidationError(error: unknown): error is ValidationError {
  return error instanceof ValidationError;
}
// Usage in catch block try { validateUser({});
} catch (error: unknown) {
if (isValidationError(error)) {
  console.error(`Validation error in ${error.field}: ${error.message}`);
} else if (isErrorWithMessage(error)) {
console.error('An error occurred:', error.message);
} else {
console.error('An unknown error occurred');
}
}

For more complex error handling, consider using a type assertion function:

function assertIsError(error: unknown): asserts error is Error {
 if (!(error instanceof Error)) {
 throw new Error('Caught value is not an Error instance');
 }
}
try {
 // ...
} catch (error) {
assertIsError(error);
console.error((error as Error).message); // TypeScript now knows error is Error
}

Async Error Handling

Handling Async/Await Errors

interface User {
  id: number;
  name: string;
  email: string;
}
// Using async/await with try/catch async function fetchUser(userId: number): Promise { try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
  throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json() as User;
} catch (error) {
if (error instanceof Error) {
  console.error('Failed to fetch user:', error.message);
}
throw error; // Re-throw to allow caller to handle
}
}
// Using Promise.catch() for error handling function fetchUserPosts(userId: number): Promise {
return fetch(`/api/users/${userId}/posts`) .then(response => {
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
return response.json();
}) .catch(error => {
console.error('Failed to fetch posts:', error);
return []; // Return empty array as fallback
});
}

Always handle promise rejections to prevent unhandled promise rejection warnings:

// Bad: Unhandled promise rejection
fetchData().then(data => console.log(data));
// Good: Handle both success and error cases
fetchData()
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));
// Or use void for intentionally ignored errors
void fetchData().catch(console.error);

React Error Boundary Component

Create an Error Boundary to catch JavaScript errors in React component trees:

import React, { Component, ErrorInfo, ReactNode } from 'react';
interface ErrorBoundaryProps {
 children: ReactNode;
 fallback?: ReactNode;
}
interface ErrorBoundaryState {
 hasError: boolean;
 error?: Error;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
 public state: ErrorBoundaryState = {
 hasError: false
 };
 public static getDerivedStateFromError(error: Error): ErrorBoundaryState {
 return { hasError: true, error };
 }
 public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
 console.error('Uncaught error:', error, errorInfo);
 // Log to error reporting service
 }
 public render() {
 if (this.state.hasError) {
 return this.props.fallback || (
 <div className="error-boundary">
 <h2>Something went wrong</h2>
 <p>{this.state.error?.message}</p>
 <button onClick={() => this.setState({ hasError: false })}>
 Try again
 </button>
 </div>
 );
 }
 return this.props.children;
}
}
// Usage
function App() {
 return (
 <ErrorBoundary fallback={<div>Oops! Something broke.</div>}>
 <MyComponent />
 </ErrorBoundary>
);
}

Always Handle Errors

Never leave catch blocks empty.

At minimum, log the error:

// Bad: Silent failure
try { /* ... */ } catch { /* empty */ }
// Good: At least log the error
try { /* ... */ } catch (error) {
 console.error('Operation failed:', error);
}

Use Specific Error Types

Create custom error classes for different error scenarios:

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

Handle Errors at the Right Level

Handle errors where you have enough context to recover or provide a good user experience:

// In a data access layer
async function getUser(id: string): Promise {
 const response = await fetch(`/api/users/${id}`);
 if (!response.ok) {
 throw new NetworkError(response.status, 'Failed to fetch user');
 }
 return response.json();
}
// In a UI component
async function loadUser() {
 try {
 const user = await getUser('123');
 setUser(user);
 } catch (error) {
 if (error instanceof NetworkError) {
 if (error.status === 404) {
 showError('User not found');
 } else {
 showError('Network error. Please try again later.');
 }
} else {
showError('An unexpected error occurred');
}
}
}

Common Pitfalls

Always handle promise rejections to prevent unhandled promise rejection warnings:

// Bad: Unhandled promise rejection
fetchData();
// Good: Handle the rejection
fetchData().catch(console.error);

In TypeScript 4.0+, caught errors are of type unknown :

// Bad: Error is of type 'unknown'
try { /* ... */ } catch (error) {
 console.log(error.message); // Error: Property 'message' does not exist on type 'unknown'
}
// Good: Narrow the type
try { /* ... */ } catch (error) {
 if (error instanceof Error) {
 console.log(error.message); // OK
 }
}

Avoid silently catching and ignoring errors without proper handling:

// Bad: Error is silently ignored
function saveData(data: Data) {
 try {
 database.save(data);
 } catch {
 // Ignore
}
}
// Better: Log the error and/or notify the user
function saveData(data: Data) {
 try {
 database.save(data);
 } catch (error) {
 console.error('Failed to save data:', error);
 showError('Failed to save data. Please try again.');
}
}

Summary

Effective error handling in TypeScript involves

  • Using try/catch blocks for synchronous code
  • Handling promise rejections with .catch() or try/catch with async/await
  • Creating custom error classes for domain-specific errors
  • Using type guards to safely work with error objects
  • Handling errors at the appropriate level in your application
  • Providing meaningful error messages to users

By following these practices, you can build more robust and maintainable TypeScript applications.

Previous

TypeScript Migration Guide

Next

TypeScript Best Practices