Skip to main content

Command Palette

Search for a command to run...

Request Cancellation: Cancel Fetch Requests

Learn: Request Cancellation: Cancel Fetch Requests

Updated
5 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Request Cancellation: Cancel Fetch Requests with AbortController

Problem

When making HTTP requests with the Fetch API, you may need to cancel them before completion. Common scenarios include:

  • User navigates away from a page
  • User clicks a "Cancel" button
  • Request takes too long (timeout)
  • New request supersedes a previous one
  • Component unmounts in React

Without cancellation, these requests continue consuming bandwidth and resources, potentially causing memory leaks or race conditions.

Solution

The AbortController API provides a standard way to cancel fetch requests. It allows you to signal one or more fetch operations to abort, stopping them mid-flight.

How It Works

  1. Create an AbortController instance
  2. Pass its signal to the fetch request
  3. Call abort() to cancel the request
  4. Handle the AbortError exception

Code Examples

Basic Cancellation

// Create controller
const controller = new AbortController();

// Make request with signal
fetch('https://api.example.com/data', {
  signal: controller.signal
})
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Request was cancelled');
    } else {
      console.error('Request failed:', error);
    }
  });

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);

Timeout Implementation

function fetchWithTimeout(url, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  return fetch(url, { signal: controller.signal })
    .then(response => {
      clearTimeout(timeoutId);
      return response.json();
    })
    .catch(error => {
      clearTimeout(timeoutId);
      if (error.name === 'AbortError') {
        throw new Error(`Request timeout after ${timeout}ms`);
      }
      throw error;
    });
}

// Usage
fetchWithTimeout('https://api.example.com/data', 3000)
  .then(data => console.log(data))
  .catch(error => console.error(error.message));

React Component with Cleanup

import { useEffect, useRef, useState } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const controllerRef = useRef(null);

  useEffect(() => {
    // Create new controller for this effect
    controllerRef.current = new AbortController();
    const controller = controllerRef.current;

    const fetchData = async () => {
      setLoading(true);
      setError(null);

      try {
        const response = await fetch('https://api.example.com/data', {
          signal: controller.signal
        });

        if (!response.ok) throw new Error('API error');
        const result = await response.json();

        // Only update state if request wasn't aborted
        if (!controller.signal.aborted) {
          setData(result);
        }
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        if (!controller.signal.aborted) {
          setLoading(false);
        }
      }
    };

    fetchData();

    // Cleanup: abort request on unmount
    return () => controller.abort();
  }, []);

  return (
    <div>
      {loading && <p>Loading...</p>}
      {error && <p>Error: {error}</p>}
      {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
    </div>
  );
}

Multiple Requests with Single Controller

const controller = new AbortController();

// Multiple requests share same signal
Promise.all([
  fetch('https://api.example.com/users', { signal: controller.signal }),
  fetch('https://api.example.com/posts', { signal: controller.signal }),
  fetch('https://api.example.com/comments', { signal: controller.signal })
])
  .then(responses => Promise.all(responses.map(r => r.json())))
  .then(data => console.log('All data:', data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('All requests cancelled');
    }
  });

// Cancel all at once
setTimeout(() => controller.abort(), 3000);

Search with Request Deduplication

class SearchManager {
  constructor() {
    this.controller = null;
  }

  async search(query) {
    // Cancel previous request
    if (this.controller) {
      this.controller.abort();
    }

    // Create new controller
    this.controller = new AbortController();

    try {
      const response = await fetch(
        `https://api.example.com/search?q=${encodeURIComponent(query)}`,
        { signal: this.controller.signal }
      );
      return await response.json();
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('Search cancelled');
        return null;
      }
      throw error;
    }
  }
}

// Usage
const searcher = new SearchManager();

// Only latest search completes
searcher.search('javascript');
searcher.search('python');  // Cancels previous
searcher.search('rust');    // Cancels previous

With Async/Await and Error Handling

async function robustFetch(url, options = {}) {
  const controller = new AbortController();
  const timeout = options.timeout || 10000;

  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error(`Request timeout after ${timeout}ms`);
    }
    throw error;
  } finally {
    clearTimeout(timeoutId);
  }
}

// Usage
try {
  const data = await robustFetch('https://api.example.com/data', {
    timeout: 5000
  });
  console.log(data);
} catch (error) {
  console.error('Fetch failed:', error.message);
}

Tips & Best Practices

✅ Do's

  1. Always handle AbortError separately

    .catch(error => {
      if (error.name === 'AbortError') {
        // Handle cancellation
      } else {
        // Handle other errors
      }
    });
    
  2. Check signal.aborted before state updates

    if (!controller.signal.aborted) {
      setState(data);
    }
    
  3. Clean up in React useEffect

    return () => controller.abort();
    
  4. Reuse controllers for related requests

    // Cancel all related requests at once
    Promise.all([fetch(url1, {signal}), fetch(url2, {signal})]);
    controller.abort();
    
  5. Combine with timeout logic

    setTimeout(() => controller.abort(), timeout);
    

❌ Don'ts

  1. Don't ignore AbortError in logging

    // Bad
    .catch(error => console.error(error)); // Logs AbortError
    
    // Good
    .catch(error => {
      if (error.name !== 'AbortError') {
        console.error(error);
      }
    });
    
  2. Don't forget to clear timeouts

    // Bad
    setTimeout(() => controller.abort(), 5000);
    
    // Good
    const id = setTimeout(() => controller.abort(), 5000);
    // ... later
    clearTimeout(id);
    
  3. Don't reuse aborted controllers

    // Bad
    controller.abort();
    fetch(url, { signal: controller.signal }); // Won't work
    
    // Good
    controller.abort();
    const newController = new AbortController();
    fetch(url, { signal: newController.signal });
    

Browser Support

  • ✅ All modern browsers (Chrome 66+, Firefox 57+, Safari 11.1+, Edge 16+)
  • ✅ Node.js 15+
  • ⚠️ IE 11 not supported

Performance Considerations

  • Minimal overhead: AbortController is lightweight
  • Memory efficient: Aborted requests release resources immediately
  • No memory leaks: Proper cleanup prevents dangling requests
  • Race condition prevention: Abort old requests before new ones

Summary

FeatureBenefit
AbortControllerStandard API for cancelling requests
signal propertyPassed to fetch to enable cancellation
abort() methodTriggers cancellation immediately
AbortErrorThrown when request is cancelled
Cleanup patternPrevents memory leaks in React
Timeout integrationAutomatic request cancellation after delay

Use AbortController to build responsive, resource-efficient applications that handle user interactions gracefully and prevent unnecessary network traffic.