Networking: Fetch, XHR, WebSockets & CORS

10 questions found

How do you make a basic GET request with fetch()?

Beginner
fetch(url) returns a Promise that resolves to a Response object once headers arrive; you then call .json() (or .text(), etc.) — itself also returning a Promise — to read and parse the actual body.
const response = await fetch('/api/users');
const users = await response.json();
console.log(users);
Real-world example Loading a list of products from an API when a page first renders.

Common follow-ups: Does fetch() reject its Promise on an HTTP error status like 404 or 500?

JSON & Data Serialization

How do you send a POST request with a JSON body using fetch()?

Beginner
Pass an options object as the second argument specifying method: 'POST', a Content-Type header, and a JSON.stringify()'d body — fetch doesn't set the Content-Type header automatically for you.
await fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Sam' })
});
Real-world example Submitting a new user registration form's data to a backend API.

Common follow-ups: What happens if you forget to set the Content-Type header?

JSON & Data Serialization

Why does fetch() NOT reject its Promise for HTTP error responses like 404 or 500?

Intermediate
fetch() only rejects for network-level failures (DNS errors, no connectivity, CORS block) — any response the server actually sends, including error status codes, resolves successfully. You must check response.ok (or response.status) yourself and throw manually if needed.
const res = await fetch('/api/missing');
if (!res.ok) {
  throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();
Real-world example A bug where a failed API call with a 500 status was silently treated as success because .ok wasn't checked.

Common follow-ups: How would you wrap fetch() in a helper that automatically throws on non-OK responses?

Error Handling

What is CORS and why does the browser block certain cross-origin requests?

Intermediate
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that blocks a web page from reading responses from a different origin (domain/port/protocol) unless the server explicitly allows it via response headers like Access-Control-Allow-Origin — this protects users from malicious sites silently reading data from other sites on their behalf.
// Server must respond with, e.g.:
// Access-Control-Allow-Origin: https://myapp.com

fetch('https://api.otherdomain.com/data'); // blocked unless server allows it
Real-world example Debugging why an API call works from Postman but fails with a 'CORS policy' error only in the browser.

Common follow-ups: What is a CORS 'preflight' OPTIONS request and when does the browser send one?

Security: XSS CSRF & Content Security Policy

How do you cancel an in-flight fetch() request?

Intermediate
Create an AbortController, pass its .signal to fetch()'s options, and call controller.abort() to cancel the request — fetch's Promise then rejects with an AbortError, which you can distinguish from real failures in your catch block.
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
  .catch(err => { if (err.name === 'AbortError') console.log('cancelled'); });

controller.abort(); // cancels the request
Real-world example Cancelling a previous search request when the user types a new character before the old one finishes.

Common follow-ups: How would you implement a fetch timeout using AbortController?

Async Iterators & Streams

What is XMLHttpRequest and why would you still use it over fetch() today?

Advanced
XHR is the older, event-based API for HTTP requests. Unlike fetch(), it natively supports upload/download progress events and can be aborted synchronously without needing the AbortController API — some libraries and progress-bar-driven upload UIs still rely on XHR for these reasons.
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.upload.onprogress = (e) => console.log(`${e.loaded}/${e.total}`);
xhr.onload = () => console.log(xhr.responseText);
xhr.send(formData);
Real-world example Showing a real-time upload progress bar for a large file, which fetch() can't do natively for uploads.

Common follow-ups: Does fetch() have any modern way to track upload progress, given this limitation?

Debugging Testing & Tooling

How does a WebSocket connection differ fundamentally from repeated fetch() requests?

Advanced
A WebSocket establishes a single persistent, full-duplex connection after an initial HTTP handshake, allowing both client and server to send messages to each other at any time with minimal overhead — unlike fetch(), which requires a brand-new HTTP request/response cycle (with its own headers and TCP overhead) for every single exchange.
const socket = new WebSocket('wss://example.com/chat');
socket.onmessage = (event) => console.log('Received:', event.data);
socket.onopen = () => socket.send('Hello server!');
Real-world example Building a live chat application or real-time collaborative editor where the server needs to push updates instantly.

Common follow-ups: How does a WebSocket's initial handshake relate to the standard HTTP protocol?

Event Loop & Concurrency

What is a CORS preflight request, and which requests trigger one?

Advanced
For 'non-simple' requests (custom headers, methods like PUT/DELETE, or a Content-Type other than a few basic ones), the browser automatically sends an OPTIONS request first, asking the server whether the actual request is allowed — only if the server responds affirmatively does the browser send the real request.
// Browser automatically sends, before your actual PUT request:
// OPTIONS /api/users/1
// Access-Control-Request-Method: PUT
// Access-Control-Request-Headers: content-type, authorization
Real-world example Understanding why a simple GET request works cross-origin without extra config, but a PUT with a custom Authorization header triggers an extra network round-trip.

Common follow-ups: How can you configure a server to respond correctly to preflight OPTIONS requests?

Security: XSS CSRF & Content Security Policy

How do Server-Sent Events (SSE) differ from WebSockets, and when would you choose them?

Advanced
SSE (via EventSource) provides a one-way stream of text events FROM server TO client over a single long-lived HTTP connection, automatically reconnecting on disconnect — simpler than WebSockets when you don't need the client to send messages back over the same connection, like live notifications or a streaming AI chat response.
const events = new EventSource('/api/notifications');
events.onmessage = (e) => console.log('New notification:', e.data);
Real-world example Streaming a live AI-generated response token-by-token to the client, or pushing live notification counts.

Common follow-ups: Why can't SSE send binary data the way WebSockets can?

Async Iterators & Streams

How would you implement request retries with exponential backoff and a total timeout using fetch()?

Advanced
Combine a retry loop around fetch() with AbortController-based per-attempt timeouts, increasing the delay between attempts exponentially, and stop retrying once either the attempt limit or overall elapsed time budget is exceeded.
async function fetchWithRetry(url, { retries = 3, timeout = 3000 } = {}) {
  for (let i = 0; i <= retries; i++) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeout);
    try {
      const res = await fetch(url, { signal: controller.signal });
      clearTimeout(timer);
      if (res.ok) return res;
    } catch (err) {
      if (i === retries) throw err;
      await new Promise(r => setTimeout(r, 500 * 2 ** i));
    } finally {
      clearTimeout(timer);
    }
  }
}
Real-world example Building a resilient API client that gracefully handles flaky mobile network connections.

Common follow-ups: How would you avoid retrying on requests that aren't idempotent, like a POST that creates a resource?

Error Handling