Mastering Advanced TypeScript Error Inference for Cleaner Code
Learn how to use TypeScript's advanced error inference to write cleaner, safer, and more maintainable code with practical examples.
TypeScript helps catch errors early by inferring types automatically. However, sometimes your code can be improved even more by mastering TypeScript's error inference capabilities. This article walks you through some advanced concepts around error inference, making your code cleaner and easier to maintain.
A core strength of TypeScript is its ability to infer types from your code. But what about inferring errors? Advanced error inference lets TypeScript catch complex bugs without you explicitly defining every detail. Let's start with a simple example.
function getUserName(user: { name?: string }) {
// TypeScript infers name might be undefined
if (!user.name) {
throw new Error("User name is missing");
}
return user.name;
}
Here, TypeScript understands that `user.name` might be undefined because it is an optional property. The check `if (!user.name)` helps TypeScript narrow down the type safely. This prevents runtime errors by forcing a check before usage.
Now, consider a function where you want to infer errors in a more dynamic scenario with conditional types and advanced inference techniques.
type Result<T> = { success: true; value: T } | { success: false; error: string };
function parseJson<T>(json: string): Result<T> {
try {
const parsed = JSON.parse(json) as T;
return { success: true, value: parsed };
} catch (e) {
return { success: false, error: (e as Error).message };
}
}
const result = parseJson<{ name: string }>(`{"name":"Alice"}`);
if (!result.success) {
// TypeScript infers this is the error case
console.error("Parsing failed:", result.error);
} else {
// TypeScript infers the value is correctly typed
console.log("Parsed name:", result.value.name);
}In this example, we define a `Result` type that can represent either success or failure. TypeScript uses this union to infer errors and success cases, enabling more precise type checking. This approach allows you to handle errors cleanly without losing type-safety.
You can also create utility types to infer errors from functions automatically. Here's a basic pattern to extract error types from promise-returning functions.
type InferError<T> = T extends (...args: any[]) => Promise<infer R>
? R extends { success: false; error: infer E }
? E
: never
: never;
async function fetchUser(): Promise<Result<{ id: number; name: string }>> {
// Example fetching user
return { success: true, value: { id: 1, name: "Alice" } };
}
// TypeScript infers the error type as string
type FetchUserError = InferError<typeof fetchUser>;
Using `InferError`, TypeScript automatically extracts the error type from the fetch function's return value, making error handling easier and more consistent across your codebase.
In summary, mastering advanced error inference in TypeScript helps you write cleaner, more maintainable, and strongly-typed error handling logic. Use union types for success/error patterns, apply conditional types to infer error types, and always leverage TypeScript's narrowing abilities for safer code.