JAVASCRIPT
Cancel In-Flight API Requests with AbortController
Learn how to use the JavaScript AbortController to cancel ongoing Fetch API requests, preventing unnecessary processing and improving user experience.
let activeAbortController = null; // Store the active controller for cancellation
async function fetchDataWithCancellation(url, customFetch = fetch) {
// If there's an existing request managed by an AbortController, cancel it
if (activeAbortController) {
activeAbortController.abort();
console.log('Previous request cancelled.');
}
// Create a new AbortController for the current request
activeAbortController = new AbortController();
const signal = activeAbortController.signal;
try {
console.log(`Fetching data from: ${url}`);
const response = await customFetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Data fetched successfully:', data);
return data;
} catch (error) {
if (error.name === 'AbortError') {
console.warn('Fetch request was aborted.');
} else {
console.error('Error fetching data:', error);
}
throw error;
} finally {
// Clear the controller reference after the request is complete or aborted
activeAbortController = null;
}
}
// Example Usage (simulating an API call with a delay):
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Type to search...';
document.body.appendChild(searchInput);
const resultDiv = document.createElement('div');
document.body.appendChild(resultDiv);
// Mock fetch function to simulate network delay and AbortController interaction
const mockApiFetch = (url, options) => {
const signal = options && options.signal;
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
if (signal && signal.aborted) {
console.log(`Mock fetch for ${url} was aborted.`);
reject(new DOMException('Aborted', 'AbortError'));
return;
}
const query = new URL(url).searchParams.get('q');
resolve({
ok: true,
status: 200,
json: () => Promise.resolve({ query: query, results: [`Result 1 for "${query}"`, `Result 2 for "${query}"`] }),
});
}, Math.random() * 1000 + 500); // Random delay between 0.5s and 1.5s
if (signal) {
signal.addEventListener('abort', () => {
clearTimeout(timeout);
reject(new DOMException('Aborted', 'AbortError'));
}, { once: true });
}
});
};
searchInput.addEventListener('input', (event) => {
const query = event.target.value.trim();
if (query.length < 2) {
resultDiv.textContent = 'Type at least 2 characters.';
if (activeAbortController) {
activeAbortController.abort();
activeAbortController = null;
}
return;
}
// Use the function with our mock fetch for demonstration
fetchDataWithCancellation(`https://api.example.com/search?q=${encodeURIComponent(query)}`, mockApiFetch)
.then(data => {
if (!activeAbortController) { // Check if not aborted by a subsequent call
resultDiv.textContent = JSON.stringify(data, null, 2);
}
})
.catch(error => {
if (error.name === 'AbortError') {
resultDiv.textContent = 'Search cancelled.';
} else {
resultDiv.textContent = 'Error: ' + error.message;
}
});
});
console.log("Type into the search box above to test request cancellation.");
How it works: This JavaScript snippet demonstrates how to use the `AbortController` API to cancel in-flight `fetch` requests. When a new request is initiated (e.g., from rapid user typing in a search box), any previous pending request managed by the shared `activeAbortController` instance is aborted. This is crucial for optimizing frontend performance and user experience by preventing stale or unnecessary data processing and reducing network load.