Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

Fetch API Deep Dive: AbortController, Streaming, CORS, keepalive, and Service Worker Interception

The Fetch API is the modern standard for making HTTP requests from JavaScript, replacing XMLHttpRequest. Firefox has full Fetch support, including the newer additions like streaming, AbortController, and credentials hand

The Fetch API is the modern standard for making HTTP requests from JavaScript, replacing XMLHttpRequest. Firefox has full Fetch support, including the newer additions like streaming, AbortController, and credentials handling. This guide covers practical patterns with Firefox-specific notes on behavior.

Basic Fetch

// GET request
const response = await fetch('/api/users');
const data = await response.json();

// POST request with JSON body
const response = await fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Alice', email: '[email protected]' })
});
const created = await response.json();

Request and Response Objects

fetch() returns a Promise that resolves to a Response object. The Promise rejects only on network failures (DNS failure, no internet) — not on HTTP error status codes (404, 500 are resolved responses).

const response = await fetch('/api/user/999');

// Always check .ok for success
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

// Reading the body — choose one:
const json = await response.json();      // Parse as JSON
const text = await response.text();      // Read as text
const blob = await response.blob();      // Read as Blob (binary)
const buffer = await response.arrayBuffer(); // Read as ArrayBuffer
const formData = await response.formData(); // Read as FormData

// Body can only be consumed ONCE — clone if you need to read twice:
const clone = response.clone();

The Request Object

For reusable request configurations:

const baseRequest = new Request('/api/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-API-Key': 'secret' },
});

// Clone to reuse
const response1 = await fetch(baseRequest.clone());
const response2 = await fetch(baseRequest.clone());

Credentials and CORS

// Include cookies in cross-origin requests (requires CORS with Allow-Credentials)
await fetch('https://api.example.com/user', {
  credentials: 'include'
});

// Options:
// 'omit' — never send cookies (default for cross-origin)
// 'same-origin' — send cookies for same-origin only
// 'include' — always send cookies

Firefox's CORS enforcement is stricter than Chrome in some edge cases. Firefox enforces the preflight cache correctly and validates the Access-Control-Allow-Origin header more rigorously. CORS issues that Chrome masks often appear first in Firefox.

AbortController

Cancel pending requests with AbortController:

const controller = new AbortController();

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

try {
  const response = await fetch('/api/slow', {
    signal: controller.signal
  });
  clearTimeout(timeout);
  const data = await response.json();
} catch (err) {
  if (err.name === 'AbortError') {
    console.log('Request was aborted');
  } else {
    throw err;
  }
}

React pattern — cancel on component unmount:

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

  fetch('/api/data', { signal: controller.signal })
    .then(r => r.json())
    .then(data => setState(data))
    .catch(err => {
      if (err.name !== 'AbortError') console.error(err);
    });

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

Streaming Responses

For large responses, read body as a stream without waiting for the full download:

const response = await fetch('/api/large-file');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let result = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  result += decoder.decode(value, { stream: true });
  // Process chunks as they arrive
  updateProgressUI(result.length);
}

Server-Sent Events alternative: for streaming where the server pushes, EventSource is cleaner. But for streaming large downloads or real-time data, response.body.getReader() gives full control.

Uploading Files

// Single file upload
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('metadata', JSON.stringify({ category: 'image' }));

const response = await fetch('/api/upload', {
  method: 'POST',
  body: formData
  // Don't set Content-Type — let browser set it with the boundary
});

// Track upload progress (not directly from fetch, use XMLHttpRequest for that)
// Or use a custom ReadableStream wrapper

Error Handling Patterns

async function apiFetch(url, options = {}) {
  const response = await fetch(url, options);

  if (!response.ok) {
    let message = `HTTP ${response.status}`;
    try {
      const errorBody = await response.json();
      message = errorBody.message || message;
    } catch {} // body might not be JSON
    throw new Error(message);
  }

  return response;
}

// Usage
try {
  const response = await apiFetch('/api/user/123');
  const user = await response.json();
} catch (err) {
  console.error('Request failed:', err.message);
}

Intercepting Fetch with Service Workers

Service workers can intercept all fetch requests from the page:

// In service worker
self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  // Serve from cache for static assets
  if (url.pathname.startsWith('/static/')) {
    event.respondWith(
      caches.match(event.request).then(cached => cached || fetch(event.request))
    );
  }
  // Let API requests pass through
});

Firefox-Specific Notes

keepalive option: Firefox 125+ supports fetch({ keepalive: true }) — keeps the request alive even after the page unloads. Useful for analytics/beacon-style requests that must complete during page exit:

window.addEventListener('beforeunload', () => {
  fetch('/api/analytics', { 
    method: 'POST', 
    body: JSON.stringify(analyticsData),
    keepalive: true 
  });
});

HTTP/3 (QUIC): Firefox's fetch automatically uses HTTP/3 when the server supports it. Check the Protocol column in Firefox's Network DevTools — "h3" indicates HTTP/3.

Priority hints: Firefox 101+ supports the priority option ('high', 'low', 'auto') to hint relative importance to the browser's resource scheduler.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.