JAVASCRIPT
Cancel Pending Fetch API Requests Using AbortController
Learn how to effectively cancel ongoing `fetch` API requests using JavaScript's `AbortController`. Improve user experience and prevent race conditions in your web applications.
const activeRequests = new Map(); // Store AbortControllers by request ID or purpose
async function makeCancellableFetch(requestId, url, options = {}) {
// If there's an existing request with the same ID, cancel it
if (activeRequests.has(requestId)) {
activeRequests.get(requestId).abort();
console.log(`Aborted previous request for ID: ${requestId}`);
}
const controller = new AbortController();
activeRequests.set(requestId, controller); // Store new controller
const fetchOptions = {
...options,
signal: controller.signal, // Link controller's signal to fetch request
};
try {
const response = await fetch(url, fetchOptions);
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log(`Request for ID ${requestId} was aborted.`);
// Propagate or handle the abort error gracefully
throw new Error('Request aborted by user/system.');
} else {
console.error(`Fetch error for ID ${requestId}:`, error);
throw error;
}
} finally {
// Clean up controller after request completes or fails (unless it was aborted externally)
if (activeRequests.get(requestId) === controller) {
activeRequests.delete(requestId);
}
}
}
// Example Usage:
// Let's simulate a search input where previous requests should be canceled if a new one starts.
// const searchInput = document.getElementById('search-box');
// let searchTimeout;
// searchInput.addEventListener('input', (event) => {
// clearTimeout(searchTimeout);
// const query = event.target.value;
// if (query.length < 3) return; // Only search for longer queries
// searchTimeout = setTimeout(() => {
// makeCancellableFetch('search-data', `/api/search?q=${encodeURIComponent(query)}`)
// .then(data => console.log('Search results:', data))
// .catch(error => {
// // Handle aborted requests gracefully, e.g., don't show an error toast
// if (error.message !== 'Request aborted by user/system.') {
// console.error('Search failed:', error);
// }
// });
// }, 300); // Debounce search input
// });
// You can also explicitly cancel a request:
// If you have a 'cancel button' for a long operation:
// const controllerForLongTask = new AbortController();
// makeCancellableFetch('long-task', '/api/long-running-process', { signal: controllerForLongTask.signal });
// setTimeout(() => {
// controllerForLongTask.abort(); // Cancel after some time
// console.log('Long task manually cancelled.');
// }, 1000);
How it works: This JavaScript snippet demonstrates how to use the `AbortController` API to cancel ongoing `fetch` requests. The `makeCancellableFetch` function creates an `AbortController` for each request, linking its `signal` to the `fetch` call. If a new request with the same `requestId` is initiated while an old one is pending, the old request's controller is used to `abort()` it. This is particularly useful for scenarios like debounced search inputs where only the latest request's results are relevant, preventing unnecessary network traffic and race conditions. Aborted requests throw an `AbortError`, which is caught and handled separately.