JavaScript Development Space

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

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.

ts
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 };
5
6 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:

ts
1 let userData: { id: number; name: string } | undefined;
2
3 try {
4 userData = await fetchData(1);
5 } catch (error) {
6 console.log("Error:", error);
7 }
8
9 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:

ts
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:

ts
1 const [user, error] = await handleAsync(fetchData(1));
2
3 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! 🚀

JavaScript Development Space

© 2025 JavaScript Development Space - Master JS and NodeJS. All rights reserved.