Loading lesson path
Concept visual
Robust error handling is crucial for building reliable TypeScript applications.
Formula
This guide covers everything from basic try/catch to advanced error handling patterns.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 + NoteIn TypeScript 4.0 and later, the unknown type is the default type for catch variables. Always narrow the type before accessing properties.
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');
}
}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');
}
}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
}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
});
}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);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 })}></button> </div>
);
}
return this.props.children;
}
}
// Usage function App() {
return (
<ErrorBoundary fallback={<div>Oops! Something broke.</div>}><MyComponent /> </ErrorBoundary>
);
}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);
}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 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');
}
}
}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
}
}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.');
}
}Effective error handling in TypeScript involves: