Understanding TypeScript Union Types to Handle Complex Errors Gracefully
Learn how to use TypeScript union types to effectively manage different error types in your applications, making your error handling clearer and safer.
Handling errors properly is essential for writing robust applications. In JavaScript, errors can be anything from simple strings to complex objects, which can make error handling tricky and unsafe. TypeScript helps by allowing us to define precise types for errors, making it easier to differentiate and handle multiple error cases safely.
One powerful feature of TypeScript is union types. Union types allow a variable to hold more than one type, which is perfect for modeling different kinds of errors that might occur in your application.
Let's say you have a function that might return either a network error or a validation error. You can define these error shapes and use a union type to represent either error.
type NetworkError = {
type: 'network';
message: string;
statusCode: number;
};
type ValidationError = {
type: 'validation';
message: string;
field: string;
};
type AppError = NetworkError | ValidationError;Here, the `AppError` type can be either a `NetworkError` or a `ValidationError`. The `type` field in each object acts as a discriminant, allowing us to safely check what kind of error we have at runtime.
Let's create a function that simulates an operation which can result in these errors:
function performOperation(): AppError | null {
// Simulate either error or success
const random = Math.random();
if (random < 0.4) {
return { type: 'network', message: 'Failed to reach server', statusCode: 500 };
} else if (random < 0.8) {
return { type: 'validation', message: 'Invalid input', field: 'email' };
}
return null; // no error
}Now, when handling the result, you can use a type guard to determine which error you received and respond accordingly:
const error = performOperation();
if (error) {
if (error.type === 'network') {
// Here TypeScript knows error is NetworkError
console.error(`Network error: ${error.message} (Status: ${error.statusCode})`);
} else if (error.type === 'validation') {
// Here TypeScript knows error is ValidationError
console.error(`Validation failed on field '${error.field}': ${error.message}`);
}
} else {
console.log('Operation succeeded without errors.');
}This pattern makes your error handling clear and safe. TypeScript helps catch mistakes, like trying to access `field` on a `NetworkError`, which wouldn't make sense.
In summary, by combining union types with discriminant properties, you can model complex error scenarios precisely. This approach leads to better type safety and cleaner code when handling multiple kinds of errors in TypeScript.