26. Error Handling in Async JavaScript
Why Async Error Handling Matters
Modern web apps rely heavily on asynchronous operations: API calls, timers, file reading, and more. Handling errors correctly ensures your app remains reliable and users get meaningful feedback instead of broken experiences.
Errors in Promises
Promises represent future results. Errors in promises are caught with `.catch()`.
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => console.log(data))
.catch(err => console.error('Fetch failed:', err.message));Async/Await with try...catch
Async/await makes asynchronous code look synchronous. Use `try...catch` to handle errors cleanly.
async function fetchUsers() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) throw new Error('Failed to fetch users');
const data = await response.json();
console.log(data);
} catch (err) {
console.error('Error fetching users:', err.message);
}
}
fetchUsers();Handling Multiple Async Operations
When running multiple async operations, each can fail independently. `Promise.allSettled` is useful to handle them without failing the entire batch.
const urls = [
'https://jsonplaceholder.typicode.com/users',
'https://jsonplaceholder.typicode.com/posts',
'https://jsonplaceholder.typicode.com/invalid'
];
async function fetchAll() {
const results = await Promise.allSettled(urls.map(url => fetch(url)));
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`Success from ${urls[i]}`);
} else {
console.error(`Failed to fetch ${urls[i]}:`, result.reason);
}
});
}
fetchAll();Retrying Failed Requests
Sometimes a network failure is temporary. You can implement retry logic to attempt the request again.
async function fetchWithRetry(url, retries = 3, delay = 1000) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error('Network error');
return await res.json();
} catch (err) {
console.warn(`Attempt ${i + 1} failed. Retrying in ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error('All retries failed');
}
fetchWithRetry('https://jsonplaceholder.typicode.com/users')
.then(data => console.log('Data fetched:', data))
.catch(err => console.error(err.message));Handling API Errors Gracefully
Always check for both network errors and API-specific errors in the response body. This ensures your app handles unexpected API responses without crashing.
async function safeApiCall(url) {
try {
const res = await fetch(url);
const data = await res.json();
if (data.error) {
throw new Error(data.error.message);
}
return data;
} catch (err) {
console.error('API call failed:', err.message);
return null;
}
}Mini Challenge
1. Fetch a list of users and their posts. Handle both network errors and invalid responses. 2. Implement retry logic for failed fetches. 3. Display errors to the user without breaking the UI. 4. Use `Promise.allSettled` to fetch multiple endpoints and handle each result individually.