Flash cards
Review the key moves
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.
Which statement best captures the main point of this lesson?
Complete the missing token from the example code.
___ divide(a: number, b: number): number {Put the learning moves in the order that makes the concept easiest to apply.
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
ErrorType 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.