# Request Cancellation: Cancel Fetch Requests

# 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

```javascript
// 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

```javascript
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

```javascript
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

```javascript
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

```javascript
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

```javascript
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**
   ```javascript
   .catch(error => {
     if (error.name === 'AbortError') {
       // Handle cancellation
     } else {
       // Handle other errors
     }
   });
   ```

2. **Check signal.aborted before state updates**
   ```javascript
   if (!controller.signal.aborted) {
     setState(data);
   }
   ```

3. **Clean up in React useEffect**
   ```javascript
   return () => controller.abort();
   ```

4. **Reuse controllers for related requests**
   ```javascript
   // Cancel all related requests at once
   Promise.all([fetch(url1, {signal}), fetch(url2, {signal})]);
   controller.abort();
   ```

5. **Combine with timeout logic**
   ```javascript
   setTimeout(() => controller.abort(), timeout);
   ```

### ❌ Don'ts

1. **Don't ignore AbortError in logging**
   ```javascript
   // 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**
   ```javascript
   // Bad
   setTimeout(() => controller.abort(), 5000);
   
   // Good
   const id = setTimeout(() => controller.abort(), 5000);
   // ... later
   clearTimeout(id);
   ```

3. **Don't reuse aborted controllers**
   ```javascript
   // 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

| Feature | Benefit |
|---------|---------|
| **AbortController** | Standard API for cancelling requests |
| **signal property** | Passed to fetch to enable cancellation |
| **abort() method** | Triggers cancellation immediately |
| **AbortError** | Thrown when request is cancelled |
| **Cleanup pattern** | Prevents memory leaks in React |
| **Timeout integration** | Automatic request cancellation after delay |

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