Fetch: Abort Β· Astro Tech Blog

Fetch: Abort

Sometimes you need to cancel an in-progress fetch β€” user navigates away, search input changes, or a request takes too long. AbortController makes this possible.

AbortController Basics

const controller = new AbortController();
const signal = controller.signal;

// Pass signal to fetch
fetch(url, { signal });

// Later, cancel the request
controller.abort();

When abort() is called, the fetch promise rejects with an AbortError.

Demo: Abort a Fetch Request
HTML
<div>
<div style='display:flex;gap:8px;margin-bottom:8px;'>
<button id='start-fetch'>Start Long Request</button>
<button id='abort-fetch'>Abort</button>
</div>
<pre id='abort-out' style='background:#f1f5f9;padding:12px;border-radius:6px;min-height:60px;'></pre>
</div>
JavaScript
const out = document.getElementById('abort-out');
let controller = null;

document.getElementById('start-fetch').onclick = async function() {
controller = new AbortController();
this.disabled = true;
out.textContent = 'Request started...\\n';

try {
// Simulate a slow endpoint by using a large delay
const response = await fetch('https://jsonplaceholder.typicode.com/posts?_delay=5000', {
signal: controller.signal
});
const data = await response.json();
out.textContent += 'Received ' + data.length + ' items';
} catch (err) {
if (err.name === 'AbortError') {
out.textContent += 'Request was aborted!';
} else {
out.textContent += 'Error: ' + err.message;
}
} finally {
this.disabled = false;
controller = null;
}
};

document.getElementById('abort-fetch').onclick = function() {
if (controller) {
controller.abort();
out.textContent += 'Abort signal sent at ' + new Date().toLocaleTimeString() + '\\n';
} else {
out.textContent = 'No active request to abort';
}
};
Live Output Window

AbortError Detection

When a request is aborted, the promise rejects with a DOMException named AbortError:

try {
  const response = await fetch(url, { signal });
} catch (err) {
  if (err.name === 'AbortError') {
    // User cancelled β€” do nothing
    return;
  }
  // Real error β€” handle it
  console.error(err);
}

Aborting Multiple Requests

One AbortController can abort multiple requests at once:

const controller = new AbortController();
const signal = controller.signal;

const [users, posts] = await Promise.all([
  fetch('/api/users', { signal }),
  fetch('/api/posts', { signal })
]);

// Abort both if one fails

Timeout with AbortController

Combine with setTimeout to implement request timeouts:

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

try {
  const response = await fetch(url, { signal: controller.signal });
  clearTimeout(timeoutId);
  // process response
} catch (err) {
  if (err.name === 'AbortError') {
    console.log('Request timed out');
  }
}
Demo: Fetch with Timeout
HTML
<div>
<button id='timeout-btn'>Fetch with 3s Timeout</button>
<pre id='timeout-out' style='background:#f1f5f9;padding:12px;border-radius:6px;min-height:60px;'></pre>
</div>
JavaScript
const out = document.getElementById('timeout-out');

document.getElementById('timeout-btn').onclick = async function() {
this.disabled = true;
out.textContent = 'Fetching with 3s timeout...\\n';

const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
out.textContent += '⏱️ Timeout reached! Aborting...\\n';
}, 3000);

try {
// Use a URL that may be slow
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1', {
signal: controller.signal
});
clearTimeout(timeoutId);
const data = await response.json();
out.textContent += 'βœ… Received: ' + data.title;
} catch (err) {
if (err.name === 'AbortError') {
out.textContent += '❌ Request aborted (timeout or cancelled)';
} else {
out.textContent += '❌ Error: ' + err.message;
}
} finally {
this.disabled = false;
}
};
Live Output Window

Cleanup in React/Components

Abort fetch when a component unmounts:

useEffect(() => {
  const controller = new AbortController();

  async function loadData() {
    try {
      const res = await fetch('/api/data', { signal: controller.signal });
      const data = await res.json();
      setData(data);
    } catch (err) {
      if (err.name !== 'AbortError') setError(err);
    }
  }

  loadData();
  return () => controller.abort(); // cleanup on unmount
}, []);

AbortController for Non-Fetch

AbortController isn’t limited to fetch β€” you can use it for any async operation:

function wait(ms, { signal } = {}) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(resolve, ms);
    if (signal) {
      signal.addEventListener('abort', () => {
        clearTimeout(timer);
        reject(new DOMException('Aborted', 'AbortError'));
      });
    }
  });
}

// Usage
const controller = new AbortController();
await wait(5000, { signal: controller.signal });

Key Takeaways

  • AbortController + signal cancels fetch requests
  • Catch AbortError specifically to distinguish cancellation from real errors
  • One AbortController can cancel multiple requests
  • Combine with setTimeout for request timeouts
  • Always clean up β€” call abort() when component unmounts
  • Check signal.aborted to see if abort has already been called
  • AbortController works with any async operation, not just fetch