Replace Repetitive Try-Catch in TypeScript: A Cleaner Way to Use safeFetch

Rehmat Sayany
4 min readSep 15, 2024

In TypeScript, handling asynchronous network requests with the fetch API often requires extensive error handling. Traditionally, developers rely on try/catch blocks to manage these errors, but that can result in repetitive and cluttered code. This blog post will introduce a cleaner way to handle network requests using a custom utility function called safeFetch, which helps you reduce repetitive code while enhancing readability and maintainability.

The Problem with Repetitive try/catch in Fetch

When making a fetch request in TypeScript, you often need to wrap your code with try/catch blocks to handle potential errors. This becomes cumbersome if you're writing multiple network requests, as you end up repeating similar logic over and over again.

Here’s a common pattern for handling fetch calls:

async function getUser() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');

if (!response.ok) {
throw new Error(`Fetch failed with status: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching user data:', error);
}
}

While this works, it involves a lot of boilerplate code, especially if you’re making several network requests. Imagine having to copy this block repeatedly for each fetch call — this leads to duplication, making your code harder to maintain.

The Solution: Using safeFetch

To address the issue of repetitive try/catch blocks, we can create a reusable function called safeFetch. This function will handle error management and return both the response data and any errors in a single, clean format.

Here’s the implementation of safeFetch in TypeScript

// Utility function to handle fetch requests with error handling
async function safeFetch<T>(promise: Promise<Response>): Promise<[T | null, any]> {
try {
const response = await promise;
// Check if the response is successful (status code 2xx)
if (!response.ok) {
return [null, `Fetch failed with status: ${response.status} - ${response.statusText}`];
}
// Parse and return the JSON data
const data: T = await response.json();
return [data, null]; // Return data and no error
} catch (error) {
return [null, error]; // Return null and the error if something went wrong
}
}

How safeFetch Works:

  • Error Handling Encapsulation: The safeFetch function wraps the fetch call in a try/catch block, meaning you no longer need to repeat this code in every request.
  • Generics for Flexibility: By using generics (<T>), we allow the function to work with any response data, whether you're fetching users, posts, or any other type of resource.
  • Tuple Return Type: The function returns a tuple: the first value is the data (if successful), and the second is the error (if there is one). This makes it easy to destructure and handle the result cleanly.

Refactoring with safeFetch

Now that we have safeFetch, let's see how it simplifies the code in our previous example:

async function getRandomUser(): Promise<void> {
// Destructure the data and error from safeFetch
const [data, error] = await safeFetch<{ id: number; name: string }>(
fetch('https://jsonplaceholder.typicode.com/users/1')
);
// Handle error if it exists
if (error) {
console.error('Error fetching user data:', error);
return;
}
// Log the data if fetch was successful
console.log('User Data:', data);
}
getRandomUser()

Key Improvements:

  1. Reduced Boilerplate: By using safeFetch, we eliminate the need to wrap our repeatedly fetch requests in try/catch blocks.
  2. Destructuring: The tuple returned by safeFetch allows for clean destructuring of the data and error, making the code easier to read and reason about.
  3. Strong Typing: With TypeScript’s generics, we ensure that the data returned by safeFetch is correctly typed, reducing runtime errors and increasing type safety.

Handling Different Data Types with safeFetch

Since safeFetch uses TypeScript generics, it can be used for different types of data. Let’s say you need to fetch a list of posts from an API. Here’s how you’d do it:

interface Post {
id: number;
title: string;
body: string;
}
async function getPosts(): Promise<void> {
// Fetch posts using safeFetch
const [data, error] = await safeFetch<Post[]>(
fetch('https://jsonplaceholder.typicode.com/posts')
);
if (error) {
console.error('Error fetching posts:', error);
return;
}
// Process the data if successful
console.log('Posts:', data);
}
getPosts();

Here, safeFetch<Post[]> ensures that the data returned is a list of Post objects. This strong typing allows TypeScript to validate the structure of the response, reducing bugs.

Why Use safeFetch?

  1. Cleaner Code: The most immediate benefit is making your code more readable and concise. You no longer need to wrap every fetch call with try/catch, as all error handling is centralized within safeFetch.
  2. Reusability: By creating a single utility function, you can reuse safeFetch for any number of network requests, reducing duplication and making your code more maintainable.
  3. Type Safety: TypeScript’s powerful type system ensures that the data returned from the fetch call matches the expected type, leading to safer and more predictable code.
  4. Consistent Error Handling: With safeFetch, you can ensure that all fetch requests are handled consistently across your application, preventing small variations in how errors are managed.

Conclusion

By replacing repetitive try/catch blocks with the safeFetch utility function, you can significantly reduce boilerplate code and improve the overall maintainability of your TypeScript codebase. safeFetch not only simplifies the process of handling network requests but also provides type safety and a cleaner way to manage errors.

Whether you’re building a small application or a large-scale TypeScript project, implementing safeFetch will make your code more concise, readable, and robust. Next time you find yourself writing repetitive try/catch blocks for fetch calls, give safeFetch a try!

--

--

Rehmat Sayany
Rehmat Sayany

Written by Rehmat Sayany

Full Stack developer @westwing passionate about NodeJS, TypeScript, React JS and AWS.

No responses yet