TypeScript Error Handling
Managing errors and exceptions in TypeScript applications
🛡️ What is Error Handling?
Error handling helps your application gracefully manage unexpected situations and failures. TypeScript enhances error handling with type safety, custom error classes, and compile-time checks to catch potential issues early.
// Basic error handling
try {
const result = riskyOperation();
console.log(result);
} catch (error) {
console.error("Something went wrong:", error);
}
Output:
Something went wrong: Error message
Error Handling Techniques
Try/Catch
Catch and handle runtime errors
try {
// risky code
} catch (error) {
// handle error
}
Custom Errors
Create specific error types
class ValidationError
extends Error {
constructor(msg: string) {
super(msg);
}
}
Type Guards
Check error types safely
if (error instanceof
ValidationError) {
// handle validation
}
Result Types
Return success or error objects
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: string }
🔹 Try/Catch/Finally
The fundamental error handling structure:
function divideNumbers(a: number, b: number): number {
try {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
}
return 0;
} finally {
console.log("Division operation completed");
}
}
console.log(divideNumbers(10, 2)); // 5
console.log(divideNumbers(10, 0)); // 0
Output:
Division operation completed 5 Error: Cannot divide by zero Division operation completed 0
🔹 Custom Error Classes
Create specific error types for different scenarios:
// Base custom error
class AppError extends Error {
constructor(
public message: string,
public statusCode: number
) {
super(message);
this.name = this.constructor.name;
}
}
// Specific error types
class ValidationError extends AppError {
constructor(message: string) {
super(message, 400);
}
}
class NotFoundError extends AppError {
constructor(resource: string) {
super(`${resource} not found`, 404);
}
}
// Using custom errors
function findUser(id: number) {
if (id < 0) {
throw new ValidationError("User ID must be positive");
}
if (id > 1000) {
throw new NotFoundError("User");
}
return { id, name: "John Doe" };
}
try {
const user = findUser(-1);
} catch (error) {
if (error instanceof ValidationError) {
console.log("Validation failed:", error.message);
} else if (error instanceof NotFoundError) {
console.log("Not found:", error.message);
}
}
Output:
Validation failed: User ID must be positive
🔹 Type-Safe Error Handling
Use TypeScript's type system for safer error handling:
// Unknown type for caught errors
function handleError(error: unknown) {
if (error instanceof Error) {
console.error("Error message:", error.message);
} else if (typeof error === "string") {
console.error("String error:", error);
} else {
console.error("Unknown error:", error);
}
}
// Type guard for error checking
function isError(value: unknown): value is Error {
return value instanceof Error;
}
// Using the type guard
try {
throw new Error("Something failed");
} catch (error) {
if (isError(error)) {
console.log("Caught error:", error.message);
}
}
Output:
Caught error: Something failed
🔹 Result Type Pattern
Return success or error without throwing exceptions:
// Define Result type
type Result<T, E = Error> =
| { success: true; value: T }
| { success: false; error: E };
// Function using Result type
function parseNumber(input: string): Result<number, string> {
const num = parseInt(input);
if (isNaN(num)) {
return {
success: false,
error: "Invalid number format"
};
}
return {
success: true,
value: num
};
}
// Using the Result type
const result1 = parseNumber("42");
if (result1.success) {
console.log("Parsed:", result1.value);
} else {
console.log("Error:", result1.error);
}
const result2 = parseNumber("abc");
if (result2.success) {
console.log("Parsed:", result2.value);
} else {
console.log("Error:", result2.error);
}
Output:
Parsed: 42 Error: Invalid number format
🔹 Async Error Handling
Handle errors in asynchronous code:
// Async function with error handling
async function fetchUserData(id: number): Promise<object> {
try {
// Simulate API call
if (id <= 0) {
throw new Error("Invalid user ID");
}
return {
id,
name: "Alice",
email: "[email protected]"
};
} catch (error) {
console.error("Failed to fetch user:", error);
throw error; // Re-throw for caller to handle
}
}
// Using async error handling
async function displayUser() {
try {
const user = await fetchUserData(1);
console.log("User:", user);
const invalidUser = await fetchUserData(-1);
} catch (error) {
if (error instanceof Error) {
console.log("Caught:", error.message);
}
}
}
displayUser();
Output:
User: { id: 1, name: "Alice", email: "[email protected]" }
Failed to fetch user: Error: Invalid user ID
Caught: Invalid user ID
🔹 Best Practices
Follow these guidelines for effective error handling:
✅ Do:
- Use specific error types for different scenarios
- Always type catch blocks as unknown
- Provide meaningful error messages for debugging
- Log errors for monitoring and debugging
- Clean up resources in finally blocks
❌ Don't:
- Swallow errors without handling them
- Use any type for error parameters
- Throw non-Error objects (use Error class)
- Ignore async errors in promises
// ❌ Bad: Swallowing errors
try {
riskyOperation();
} catch (error) {
// Silent failure - bad!
}
// ✅ Good: Proper error handling
try {
riskyOperation();
} catch (error) {
if (error instanceof Error) {
console.error("Operation failed:", error.message);
// Take appropriate action
}
}