TypeScript Utility Types You Didn’t Know You Could Leverage for Error Handling
Discover how TypeScript’s built-in utility types can simplify and improve error handling in your applications with practical examples for beginners.
Error handling is a crucial part of writing robust TypeScript applications. While many developers focus on try-catch blocks or custom error classes, TypeScript offers several powerful utility types that can help you handle errors more effectively and elegantly at the type level. In this article, we'll explore some of these utility types and show you practical ways to leverage them.
One of the most common challenges is representing a function that can either return a valid result or an error object. Traditionally, this is done using union types like `T | Error`. TypeScript utility types can improve this pattern, ensuring safer and clearer handling of success and error types.
Let's start by exploring how the `ReturnType` and `Exclude` utility types can help in error handling.
`ReturnType` extracts the return type of a function, which can be useful when you want to handle different outcomes of that function. Suppose you have a function that returns either a `User` object or a `CustomError` type:
type User = {
id: number;
name: string;
};
type CustomError = {
message: string;
code: number;
};
function fetchUser(id: number): User | CustomError {
if (id === 1) {
return { id: 1, name: "Alice" };
} else {
return { message: "User not found", code: 404 };
}
}We can capture the return type using `ReturnType
type FetchUserReturn = ReturnType<typeof fetchUser>;
// To get the success type (User) only:
type Success = Exclude<FetchUserReturn, CustomError>;
// To get the error type only:
type Failure = Extract<FetchUserReturn, CustomError>;
// Usage example
function handleResult(result: FetchUserReturn) {
if ("message" in result) {
// result is inferred as CustomError
console.error(`Error: ${result.message} (code: ${result.code})`);
} else {
// result is inferred as User
console.log(`User fetched: ${result.name}`);
}
}Another handy utility type for error handling is `Partial
type ErrorDetails = {
message: string;
code: number;
stack?: string;
};
// Sometimes you might receive only part of the error information
const partialError: Partial<ErrorDetails> = {
message: "Something went wrong"
};`Required
function logCompleteError(error: Required<ErrorDetails>) {
console.error(`Error: ${error.message}, Code: ${error.code}, Stack: ${error.stack}`);
}Lastly, consider `Record
const errorMessages: Record<number, string> = {
404: "Not Found",
500: "Internal Server Error",
401: "Unauthorized"
};
function getErrorMessage(code: number): string {
return errorMessages[code] || "Unknown error";
}Using TypeScript’s utility types improves the safety and clarity of your error handling logic, catching mistakes early through static typing. As you grow your TypeScript skills, try combining these utilities to create robust error handling solutions that make your code more predictable and maintainable.