bugl
bugl
HomeLearnPatternsSearch
HomeLearnPatternsSearch

Loading lesson path

Learn/TypeScript/TypeScript Core
TypeScript•TypeScript Core

TypeScript Error Handling

Concept visual

TypeScript Error Handling

visit parent, then branch83161014

Robust error handling is crucial for building reliable TypeScript applications.

Formula

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

Basic Error Handling

Try/Catch Blocks

The foundation of error handling in TypeScript:

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);
}

Formula

TypeScript 4.0 + Note

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

Creating Custom Error Classes

Extend the built-in

Formula

Error class to create domain - specific errors:
class ValidationError extends Error {
constructor(message: string, public field?: string) {
super(message);
this.name = 'ValidationError';

// Restore prototype chain

Object.setPrototypeOf(this, ValidationError.prototype);
}
}
class DatabaseError extends Error {
constructor(message: string, public code: number) {
super(message);
this.name = 'DatabaseError';
Object.setPrototypeOf(this, DatabaseError.prototype);
}
}
// Usage function validateUser(user: any) {
if (!user.name) {
throw new ValidationError('Name is required', 'name');
}
if (!user.email.includes('@')) {
throw new ValidationError('Invalid email format', 'email');
}
}

Type Guards for Errors

Type Predicates for Error Handling

Create type guards to safely work with different error types: // 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');
}
}

Type Assertion Pattern

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

Formula

Proper error handling in async/await code requires wrapping await calls in try/catch blocks:
interface User {
id: number;
name: string;
email: string;
}

Formula

// 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
});
}

Unhandled Promise Rejections

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);

Error Boundaries in React

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 || (

Formula

< 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>

);
}

Best Practices

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

Not Handling Promise Rejections

Always handle promise rejections to prevent unhandled promise rejection warnings: // Bad: Unhandled promise rejection fetchData(); // Good: Handle the rejection fetchData().catch(console.error);

Catching Without Proper Type Narrowing

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
}
}

Swallowing Errors

Avoid silently catching and ignoring errors without proper handling: // Bad: Error is silently ignored function saveData(data: Data) {

try {
database.save(data);
} catch {

// Ignore

}
}

Formula

// 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:

Previous

TypeScript Migration Guide

Next

TypeScript Best Practices