CalcSnippets Search
JavaScript 3 min read

AJAX Tutorial: Fetch API vs XMLHttpRequest for Modern Web Apps

Compare Fetch API and XMLHttpRequest with practical examples for requests, errors, JSON, cancellation, uploads, progress, and browser behavior.

AJAX means updating pages without a full reload

AJAX is the long-standing idea of making HTTP requests from the browser and updating part of a page without loading a completely new document. Modern developers usually do this with the Fetch API, but older codebases may still use XMLHttpRequest. Understanding both helps when maintaining legacy applications or debugging browser network behavior.

Fetch is promise-based and fits naturally with async/await. It reads cleanly for JSON APIs, simple form submissions, and modern frontend code. XMLHttpRequest is older and more verbose, but it still appears in libraries, legacy apps, upload progress flows, and environments that predate Fetch adoption.

Fetch is the modern default

A basic Fetch request can call an endpoint, await the response, check the status, and parse JSON. The important detail is that Fetch only rejects the promise for network-level failures, not for HTTP error status codes. A 404 or 500 response still resolves, so application code must check response.ok or the status explicitly.

Fetch also supports headers, request bodies, credentials, abort signals, streaming in some contexts, and integration with modern browser APIs. For most new web application code, Fetch is clearer and easier to compose than XMLHttpRequest.

  • Use Fetch for most modern JSON and API requests.
  • Check HTTP status explicitly instead of assuming promise rejection means all failures.
  • Use AbortController to cancel requests that are no longer needed.
  • Keep loading, error, empty, and retry states visible in the UI.

XMLHttpRequest still matters in older and specialized code

XMLHttpRequest can report upload and download progress events in a familiar way, which is why it still appears in file upload code. Some legacy libraries and older enterprise applications rely on it. If you maintain such code, focus on making behavior clear: open the request, set headers, handle load and error events, and parse responses carefully.

Do not mix request styles randomly in one feature. A codebase can support both during migration, but each module should have a clear pattern. Shared request helpers can standardize authentication headers, error handling, JSON parsing, and retry behavior.

Design around user experience

The network is unreliable. Users may change pages, lose connectivity, submit twice, or wait on slow mobile connections. AJAX code should handle cancellation, duplicate submissions, timeouts, validation errors, and stale responses. Good request code is not only about calling an endpoint. It is about making the interface stay understandable while waiting.

Fetch and XMLHttpRequest are tools. The stronger skill is designing browser requests that fail clearly, protect user intent, and keep data consistent with what the user sees on screen.

Centralize request behavior where it helps

Many applications benefit from a small request helper that adds authentication headers, parses JSON consistently, handles known error formats, and reports failures to monitoring. This does not need to become a heavy framework. The goal is to avoid every button, form, and page inventing its own network behavior.

For global products, remember that users may be on slow mobile networks, corporate proxies, or high-latency connections. Clear retry behavior, accessible status messages, and protection against duplicate submissions matter as much as the request API itself. A clean Fetch call is useful, but a resilient user flow is what people actually experience.

Keep reading

Related guides