Fetch API - Exercise 3
Pass an AbortSignal to fetch and call abort() when the request is no longer useful. Handle both network failures and unsuccessful HTTP status codes before using the response data.
const controller = new AbortController();
fetch('/api/search', { signal: controller.signal });
controller.abort();Check for the AbortError name and avoid showing an error message for intentional cancellation. Handle both network failures and unsuccessful HTTP status codes before using the response data.
try {
await fetch('/api/search', { signal: controller.signal });
} catch (error) {
if (error.name !== 'AbortError') showError(error);
}Abort the request from a timer and clear that timer after the request settles. Handle both network failures and unsuccessful HTTP status codes before using the response data.
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
return await fetch(url, { signal: controller.signal });
} finally {
clearTimeout(timeout);
}Retry a bounded number of times and wait between attempts; do not retry every client or validation error. Handle both network failures and unsuccessful HTTP status codes before using the response data.
for (let attempt = 1; attempt <= 3; attempt++) {
const response = await fetch(url);
if (response.ok) return response.json();
if (response.status < 500) break;
await new Promise(resolve => setTimeout(resolve, attempt * 500));
}Abort the previous controller before starting the next search and create a fresh controller for the new request. Handle both network failures and unsuccessful HTTP status codes before using the response data.
let activeController;
async function search(query) {
activeController?.abort();
activeController = new AbortController();
return safeFetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal: activeController.signal
});
}Process a queue in batches instead of starting every request at once. Handle both network failures and unsuccessful HTTP status codes before using the response data.
for (let i = 0; i < urls.length; i += 3) {
const batch = urls.slice(i, i + 3);
await Promise.all(batch.map(url => safeFetch(url)));
}Treat server data as untrusted and verify the fields your UI needs before rendering. Handle both network failures and unsuccessful HTTP status codes before using the response data.
function isUser(value) {
return value && typeof value.id === 'number' && typeof value.name === 'string';
}
if (!isUser(data)) throw new Error('Invalid user response');Use Promise.allSettled() when one failed request should not discard successful results. Handle both network failures and unsuccessful HTTP status codes before using the response data.
const results = await Promise.allSettled([
safeFetch('/api/profile'),
safeFetch('/api/notifications')
]);
const successful = results.filter(result => result.status === 'fulfilled');Abort the previous request before starting a new one so an older response cannot overwrite newer results.
let controller;
async function search(query) {
controller?.abort();
controller = new AbortController();
return fetch(`/api/search?q=${query}`, { signal: controller.signal });
}Set a timeout when the UI cannot wait indefinitely, and treat the resulting abort as a recoverable failure.
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try { await fetch('/api/report', { signal: controller.signal }); }
finally { clearTimeout(timeout); }