Handling Network Request Failures Gracefully in TypeScript Real-World Applications
Learn how to handle network request failures gracefully in TypeScript by using practical error handling techniques and retry patterns to improve the user experience.
When building real-world applications, network requests can fail due to various reasons like server outages, intermittent connectivity, or invalid responses. Handling these failures gracefully is crucial to provide a smooth user experience and avoid unexpected crashes.
In this article, we'll explore beginner-friendly ways to catch and manage network request errors in TypeScript. We'll also cover how to implement simple retry logic and display meaningful error messages so your app can recover from common network issues.
A common way to make network requests in TypeScript (or JavaScript) is using the `fetch` API. However, `fetch` only rejects a promise on network failure, not on HTTP errors like 404 or 500. So, we need to handle both kinds gracefully.
async function fetchData(url: string): Promise<any> {
try {
const response = await fetch(url);
if (!response.ok) {
// HTTP error (like 404, 500)
throw new Error(`Request failed with status ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
// Network error or thrown HTTP error
console.error('Fetch error:', error);
throw error; // rethrow so callers know there was a failure
}
}In this example, we check the `response.ok` property to detect HTTP errors and throw an error manually. We surround this with a try/catch block to handle both fetch promise rejections and errors we throw ourselves. Logging the error helps with debugging.
Sometimes, transient network failures resolve if retried. Let's implement a simple retry mechanism with exponential backoff to improve success rates without overwhelming the server.
async function fetchWithRetry(
url: string,
retries: number = 3,
backoff: number = 500
): Promise<any> {
try {
return await fetchData(url);
} catch (error) {
if (retries > 0) {
console.log(`Retrying... attempts left: ${retries}`);
await new Promise((resolve) => setTimeout(resolve, backoff));
return fetchWithRetry(url, retries - 1, backoff * 2); // exponential backoff
} else {
throw error; // no retries left, propagate error
}
}
}This `fetchWithRetry` function tries to fetch data, and if it fails, waits for a bit and retries. The wait time doubles each retry, limiting the chance of hammering the server.
Finally, when calling these functions from your UI or other components, always handle errors gracefully. For example, show user-friendly messages or fallback UI instead of crashing.
async function loadUserData() {
try {
const user = await fetchWithRetry('https://api.example.com/user');
console.log('User data:', user);
// Update UI with user data
} catch (error) {
console.error('Failed to load user data:', error);
alert('Sorry, we could not fetch your data. Please try again later.');
}
}
loadUserData();To summarize, handling network request failures gracefully involves: - Checking for HTTP errors explicitly - Using try/catch blocks to handle both network and application errors - Implementing retry logic with backoff - Displaying meaningful error messages in the UI Following these tips in TypeScript will help you build more resilient and user-friendly applications.