Fetch API

Med

The Fetch API provides a modern, Promise-based interface for making HTTP requests. A critical gotcha: fetch only rejects on network failures, NOT on HTTP error status codes like 404 or 500 — you must check response.ok yourself.

Interactive Visualization

DOM Events

document>
body>
div>
button
Phase: Capture
Event travels DOWN from document to target

Key Points

  • fetch() returns a Promise<Response> — does NOT reject on HTTP errors (404, 500)
  • Always check response.ok or response.status before parsing the body
  • Body parsing methods: json(), text(), blob(), arrayBuffer(), formData()
  • Request options: method, headers, body, mode, credentials, signal
  • AbortController.signal cancels in-flight requests via the signal option
  • CORS: cross-origin requests require server-side Access-Control-Allow-Origin headers

Code Examples

Basic GET with Error Handling

async function fetchData(url) {
  const response = await fetch(url);

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

  return response.json();
}

try {
  const data = await fetchData('/api/users');
  console.log(data);
} catch (err) {
  console.error('Request failed:', err.message);
}

The biggest fetch gotcha: it only rejects on network failure. Always check response.ok.

POST, PUT, and DELETE

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

// DELETE
await fetch('/api/users/123', { method: 'DELETE' });

// POST with FormData (file uploads)
const form = document.querySelector('form');
await fetch('/api/upload', {
  method: 'POST',
  body: new FormData(form), // Content-Type set automatically
});

Set method, headers, and body for non-GET requests. FormData handles file uploads automatically.

AbortController Integration

async function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal,
    });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response;
  } finally {
    clearTimeout(timeout);
  }
}

AbortController cancels in-flight requests. Pass controller.signal to fetch, call abort() to cancel.

Parallel Requests

async function fetchMultiple(urls) {
  const responses = await Promise.all(
    urls.map(url => fetch(url))
  );

  for (const res of responses) {
    if (!res.ok) throw new Error(`Failed: ${res.url}`);
  }

  return Promise.all(responses.map(r => r.json()));
}

const [users, posts] = await fetchMultiple([
  '/api/users',
  '/api/posts',
]);

Promise.all fires multiple requests in parallel for better performance.

Common Mistakes

  • Assuming fetch rejects on HTTP errors like 404 or 500
  • Forgetting Content-Type: application/json header when sending JSON
  • Reading the response body twice (body is a stream, consumed once)
  • Not handling AbortError separately from other errors

Interview Tips

  • Lead with the biggest gotcha: fetch resolves on 404, only rejects on network failure
  • Compare fetch to XMLHttpRequest: cleaner API, Promise-based
  • AbortController provides request cancellation
  • CORS basics: same-origin default, cross-origin needs server headers

Related Concepts