TypeScript Error Handling Evolution: From Try/Catch to Result Types
Add to your RSS feed2 February 20252 min read![TypeScript Error Handling Evolution: From Try/Catch to Result Types](/static/93bcd1476180fead309c533f0042a507/658d1/ts-error-handling.webp)
Table of Contents
Error handling is a crucial part of any application, but traditional try/catch
blocks in TypeScript can be cumbersome and introduce unintended side effects. In this article, we explore a modern approach to error handling using a utility function that enhances code readability and robustness.
The Problem with Traditional Try/Catch
1. Catching Unintended Errors
When using try/catch, all errors inside the try block are caught, even if they are unrelated to the function we intend to monitor.
1 const fetchData = async (id: number): Promise<{ id: number; name: string }> => {2 if (id === 2) throw new Error("User not found");3 return { id, name: "Alice" };4 };56 try {7 const user = await fetchData(1);8 console.log(usr.name); // ❌ Typo (usr instead of user)9 } catch (error) {10 console.log("An error occurred");11 }
The catch
block executes for both API errors and developer mistakes (like the typo above), making it harder to distinguish between real failures and simple bugs.
2. Mutability Pitfalls with let
A common workaround is declaring the variable outside the try/catch
block:
1 let userData: { id: number; name: string } | undefined;23 try {4 userData = await fetchData(1);5 } catch (error) {6 console.log("Error:", error);7 }89 console.log(userData?.name); // Could be undefined
This introduces risks:
- Unintentional reassignments: The variable remains mutable and could be overwritten.
- Undefined values: If an error occurs, userData remains undefined, leading to potential runtime issues.
A Better Way: Using a handleAsync Utility
We can improve error handling by wrapping promises in a reusable function:
1 const handleAsync = async <T>(promise: Promise<T>): Promise<[T, null] | [null, Error]> => {2 try {3 const data = await promise;4 return [data, null];5 } catch (error) {6 return [null, error instanceof Error ? error : new Error(String(error))];7 }8 };
Using handleAsync
Now, we can handle errors explicitly:
1 const [user, error] = await handleAsync(fetchData(1));23 if (error) {4 console.error("Error:", error.message);5 } else {6 console.log("User:", user.name);7 }
Why This Approach Is Better
✔ Explicit error handling – Forces developers to check errors before using values. ✔ Prevents unintended mutations – user remains immutable. ✔ Cleaner and more readable – Separates error handling from function execution.
Conclusion
By replacing try/catch
with a structured utility like handleAsync
, we create a more maintainable, predictable, and readable approach to error handling in TypeScript. Would you adopt this method in your next project? Let’s discuss! 🚀