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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs