XMLHttpRequest
XMLHttpRequest (XHR) is a JavaScript API that provides client-side functionality for transferring data between a web browser and a server using HTTP or similar protocols, enabling asynchronous communication without requiring a full page reload.[1][2] Its name originates from its initial design for handling XML data, though it now supports diverse formats including JSON, text, binary data (such as ArrayBuffer and Blob), and HTML.[1][3]
Originally developed by Microsoft in 1999 as an ActiveX component in the MSXML library for enhancing Outlook Web Access in Internet Explorer 5.0, XMLHttpRequest allowed for dynamic server interactions in web applications.[3][1] It gained widespread adoption when other browsers like Mozilla, Safari, and Opera implemented native versions, establishing it as a de facto standard by the early 2000s.[3] In 2006, the World Wide Web Consortium (W3C) published the first specification, formalizing the API.[4] The current living standard is maintained by the WHATWG, incorporating enhancements from XMLHttpRequest Level 2 (introduced in 2008), such as support for cross-origin resource sharing (CORS), progress events, timeouts, and file uploads.[1][3]
XMLHttpRequest played a pivotal role in the development of Asynchronous JavaScript and XML (AJAX), a web development technique coined by Jesse James Garrett in 2005 that revolutionized user interfaces by enabling seamless, real-time updates to web pages. Key methods include open() for initializing requests, send() for transmitting data, and abort() for cancellation, while properties like readyState, status, and response facilitate monitoring and handling responses.[2][1] Although largely superseded by the modern Fetch API for new development, XMLHttpRequest remains widely supported across browsers and is essential for legacy code, Web Workers, and scenarios requiring fine-grained control over HTTP requests.[2][3]
History and Development
Origins in Microsoft Technology
The XMLHttpRequest technology originated as a proprietary Microsoft innovation developed in 1999 by engineer Shawn Bracewell as part of the Outlook Web Access (OWA) team for Microsoft Exchange Server.[5] This component, initially named XMLHTTP, was created to enable asynchronous communication between the web client and the Exchange server, allowing OWA to fetch and update data without requiring full page reloads.[5] The effort was funded by the Exchange team to meet OWA's requirements for dynamic, real-time interactions in a browser-based email interface.[5]
Microsoft first publicly implemented XMLHTTP as an ActiveX object called "Microsoft.XMLHTTP" in Internet Explorer 5.0, released on March 18, 1999. Integrated into the MSXML 2.0 library, it provided developers with a programmatic way to send HTTP requests and handle XML responses directly from JavaScript within IE.[6] This marked the debut of client-side asynchronous data retrieval in a major browser, laying the groundwork for more responsive web applications.[3]
In its early years, XMLHTTP found primary use in enhancing dynamic web experiences, such as updating email inboxes or form validations without interrupting the user's view of the page—capabilities that foreshadowed modern single-page applications but predated the widespread adoption of the term "AJAX" in 2005.[5] For instance, OWA leveraged it to rewrite its entire client-side interface using Dynamic HTML (DHTML) techniques, enabling seamless data refreshes that preserved document state on the client.[5] As an ActiveX-based feature, however, it remained exclusive to Internet Explorer, limiting its accessibility to developers targeting that browser and hindering broader web innovation due to the absence of native equivalents in competitors like Netscape.[3]
This proprietary status persisted until the mid-2000s, when other browsers began implementing compatible versions, starting with Mozilla's native XMLHttpRequest in Gecko around 2000 and gaining momentum with Safari in 2004, which collectively spurred cross-browser support by 2003–2005.[3]
Standardization by W3C and WHATWG
The transition of XMLHttpRequest from a proprietary Microsoft technology to an open web standard began with independent implementations by other browser vendors, notably Mozilla, which developed a native JavaScript version in its Gecko engine around 2000, facilitating broader adoption beyond Internet Explorer.[3] This effort helped popularize the API, prompting formal standardization efforts to ensure interoperability across browsers.
In 2006, the World Wide Web Consortium (W3C) published its initial specification for XMLHttpRequest as part of the Web Applications 1.0 working draft, defining the core API for scripted HTTP client functionality.[7] Concurrently, the Web Hypertext Application Technology Working Group (WHATWG) incorporated XMLHttpRequest into its HTML living standard, initially drawing from existing implementations and maintaining it as an evolving specification since 2006.[1]
The WHATWG continued to update the standard, merging enhancements from the W3C's XMLHttpRequest Level 2 draft by the end of 2011, which introduced features like upload progress events and timeout support to improve asynchronous request handling.[1][8] By 2012, maintenance of the specification returned fully to the WHATWG as a living standard, ensuring ongoing compatibility with evolving web platforms.[1]
Browser adoption accelerated during this period, with key milestones including support in Firefox 1.0 (November 2004), Safari 1.2 (April 2004), Opera 8.0 (April 2005), and full implementation in Internet Explorer 7 (October 2006), enabling widespread use of the standardized API.[3]
The specification defines the XMLHttpRequest interface using Web IDL, inheriting from XMLHttpRequestEventTarget and exposing methods such as open(), send(), and abort(), along with attributes like readyState, status, and response, to facilitate integration with the Document Object Model (DOM) for dynamic web applications.[1] This IDL-based definition ensures the API's exposure on the Window object and in worker contexts, aligning it with broader DOM standards for consistent scripting behavior across environments.[9][1]
Core Functionality
Object Creation and Properties
The XMLHttpRequest object is instantiated in modern web browsers using the constructor new XMLHttpRequest(), which creates a new instance and initializes it with an associated XMLHttpRequestUpload object for upload progress tracking.[1] In legacy versions of Internet Explorer (prior to version 7), compatibility required falling back to ActiveXObject creation, such as new ActiveXObject("Microsoft.XMLHTTP") or new ActiveXObject("Msxml2.XMLHTTP") for later versions, to ensure cross-browser support in older environments.[2]
Core properties of the XMLHttpRequest object manage its state, response data, and configuration. The readyState property, a read-only unsigned short, indicates the current phase of the request lifecycle with discrete values: 0 for UNSENT (object just constructed), 1 for OPENED (after open() invocation), 2 for HEADERS_RECEIVED (response headers available), 3 for LOADING (response body downloading), and 4 for DONE (operation complete, successful or failed).[1] The status property, also read-only and an unsigned short, holds the HTTP response status code from the server, such as 200 for success or 404 for not found, while statusText provides the corresponding textual message as a read-only ByteString, like "OK" or "Not Found".[1] For response content, the responseType property is a DOMString that specifies the type of response expected, with possible values: the empty string (default, equivalent to "text"), "arraybuffer", "blob", "document", "json", or "text". The read-only response property then returns the response in the corresponding format: a USVString for "text" or empty, ArrayBuffer for "arraybuffer", Blob for "blob", Document for "document", or parsed JSON for "json"; it is null if the type is unsupported or the request failed. Additionally, responseText returns the raw response as a read-only USVString when responseType is empty or "text", and responseXML parses it as a read-only Document object if responseType is empty or "document", enabling XML manipulation.[1][2]
Request configuration properties include timeout, an unsigned long that sets the maximum time in milliseconds before the request aborts (default 0 for no timeout), which throws an "InvalidAccessError" if set during synchronous requests in window contexts.[1] The open() method initializes the request with parameters: a required ByteString method (e.g., "GET" or "POST"), a required USVString URL, an optional boolean for asynchronous mode (true by default), and optional USVString username and password for authentication.[1] Custom headers are added via setRequestHeader(name, value), where both arguments are ByteStrings; this appends to existing headers but throws "InvalidStateError" if the readyState is not OPENED or if the request has already been sent.[1]
Event handling for state changes is facilitated by the onreadystatechange property, an event handler that fires whenever readyState updates (except from UNSENT), allowing developers to monitor progress without deeper event system integration.[1][2]
Methods for Request Management
The open() method initializes a new request, specifying the HTTP method, target URL, and other parameters to prepare the XMLHttpRequest object for transmission. It takes five parameters: the HTTP method as a ByteString (e.g., "GET", "POST"), the request URL as a USVString, an optional boolean for asynchronous mode (defaulting to true), and optional username and password for authentication as USVStrings. Upon invocation, it terminates any ongoing fetch, sets the object's ready state to 1 (OPENED), and may throw exceptions such as SyntaxError for invalid inputs, SecurityError for forbidden methods like CONNECT in certain contexts, or InvalidAccessError if synchronous requests are restricted.[10]
The send() method transmits the prepared request to the server, optionally including a request body for methods like POST or PUT. It accepts a single optional parameter for the body, which can be null (default), a string, a Document, a Blob, or other XMLHttpRequestBodyInit types, though bodies are ignored for GET and HEAD requests. This method can only be called when the ready state is 1 (OPENED) and no prior request has been sent; otherwise, it throws an InvalidStateError. Invoking send() initiates the fetch process, updating the ready state to 2 (HEADERS_RECEIVED) upon receiving headers and 3 (LOADING) as the body loads, or 4 (DONE) upon completion.[11]
To cancel an ongoing request, the abort() method can be used, which immediately halts any network activity associated with the XMLHttpRequest object. It requires no parameters and returns undefined, triggering the abort steps that reset the ready state to 0 (UNSENT) and dispatch an abort event if the request was active. This method is particularly useful for user-initiated cancellations or resource management in asynchronous operations.[12]
The overrideMimeType() method allows developers to force a specific MIME type interpretation for the response, overriding the server's declared type. It takes a single DOMString parameter specifying the desired MIME type (e.g., "text/plain" or "application/json") and must be called before the request is sent, throwing an InvalidStateError if the ready state is 3 (LOADING) or 4 (DONE). If the provided MIME type is invalid, it defaults to "application/octet-stream"; this does not alter the actual response headers but affects how the responseText or response properties are parsed.[13]
For accessing response metadata after the request completes, XMLHttpRequest provides getAllResponseHeaders() and getResponseHeader() methods, introduced in Level 2 of the specification. The getAllResponseHeaders() method returns a ByteString containing all response headers as a single, newline-separated string (e.g., "Content-Type: text/html\r\nContent-Length: 123"), with headers sorted alphabetically and combined per the Fetch Standard's filtering rules; it returns null if no headers are available or the ready state is not 4 (DONE). Meanwhile, getResponseHeader(name) retrieves the value of a specific header by ByteString name (case-insensitive), returning a ByteString or null if absent, also adhering to Fetch Standard filters that exclude forbidden headers like Set-Cookie. These methods enable programmatic inspection of server responses without relying on full body parsing.[14][15]
Request and Response Handling
Configuring and Sending Requests
To configure and send an XMLHttpRequest, the process begins with creating an instance of the XMLHttpRequest object using the constructor new XMLHttpRequest().[16] This object provides the interface for initiating HTTP requests from JavaScript in web browsers.
The next step is to initialize the request by calling the open() method, which specifies the HTTP method (such as "GET" or "POST"), the target URL, and an optional asynchronous flag. The method signature is open(method, url [, async [, username [, password]]]), where the async parameter defaults to true for non-blocking operation; if set to false, the request operates in synchronous mode.[10][17] For example, xhr.open("GET", "https://example.com/api/data", true); prepares a GET request to the specified endpoint asynchronously. This method must be invoked before setting headers or sending the request, and it throws exceptions for invalid inputs, such as malformed URLs or forbidden methods like CONNECT.[10]
Optional custom headers can then be added using the setRequestHeader(name, value) method, which appends or overrides headers after open() but before send(). For instance, xhr.setRequestHeader("Content-Type", "application/[json](/page/JSON)"); sets the content type for a JSON payload. This method enforces restrictions, throwing errors for invalid header names or values, and cannot be called once the request has been sent.[18][19]
The request is dispatched by invoking the send(body) method, where body is an optional parameter that can be null, a string, a Blob, FormData, or other supported types; for methods like GET or HEAD, the body is ignored.[11][20] In asynchronous mode (the default), send() returns immediately, allowing the script to continue execution while the request processes in the background. Conversely, synchronous mode (async = false) blocks the calling thread until the response is received, enabling direct access to response properties immediately after send(), but this is deprecated in main browser contexts due to its potential to freeze the user interface.[21][22]
For handling request bodies, GET requests typically encode parameters directly in the URL (e.g., via query strings like ?key=value), while POST or PUT requests pass data through the send() body. Common approaches include URL-encoding form data as a string, using FormData for multipart/form-data submissions, or serializing objects with JSON.stringify() for JSON payloads, often paired with the appropriate Content-Type header.[23][24]
Best practices emphasize using asynchronous mode exclusively in user-facing contexts to prevent UI blocking, as synchronous requests are deprecated and restricted in the main thread but allowed in web workers and trigger deprecation warnings in developer tools.[21][22] For authenticated requests, set the withCredentials property to true before open() to include credentials like cookies or authorization headers in cross-origin scenarios, provided the server supports it.[25] Additionally, to avoid caching issues with GET requests, append a unique parameter such as a timestamp to the URL.[23]
Processing Responses and Error Handling
Once the XMLHttpRequest object has completed its request, developers can access the response data through specific properties. The responseText property returns the response body as a string, suitable for plain text or JSON data, but it throws an "InvalidStateError" if the responseType is set to anything other than "" or "text".[26] For JSON responses, this string must be parsed using the JSON.parse() method to convert it into a JavaScript object, enabling further manipulation of the data. The responseXML property provides the response as a Document object if the content is XML or HTML, allowing direct DOM traversal; otherwise, it returns null, and developers may need to use the DOMParser API to parse non-XML strings into a Document for processing.[27][28]
Progress monitoring during and after the request relies on the readyState property, which indicates the current phase: 0 (UNSENT), 1 (OPENED), 2 (HEADERS_RECEIVED), 3 (LOADING), or 4 (DONE).[29] The onreadystatechange event handler is triggered whenever readyState changes, allowing developers to check for completion by verifying if readyState equals 4 and the HTTP status is in the 200-299 range, which signifies a successful response.[30] For asynchronous requests, additional Level 2 event handlers provide more granular control: onload fires upon successful completion, onprogress reports upload/download progress via the ProgressEvent interface, onloadstart signals the start of the request, and onloadend indicates the end regardless of outcome.[31]
Error detection involves examining the status property and relevant events. Network errors, such as connection failures, result in a status of 0 and trigger the onerror event, where the response property is set to a network error value.[32] HTTP client errors (4xx) or server errors (5xx) are identified via the status code after readyState reaches 4, with statusText providing the corresponding message. Timeouts are managed by setting the timeout property to a millisecond value before sending the request; if exceeded, the ontimeout event fires, and the request is aborted automatically.[33] The onerror event also handles general failures, including aborts initiated by the abort() method, which sets readyState to 0 and triggers an "AbortError".[34]
Common pitfalls in response processing include handling empty responses, where responseText or responseXML may be empty strings or null even on successful statuses, requiring explicit checks before parsing to avoid runtime errors. Charset mismatches can occur if the server's content-type header specifies an encoding not matching the response data, leading to garbled responseText; this is mitigated by using the overrideMimeType() method to force a specific MIME type and charset before sending.[35] Abort-induced errors from calling abort() during an ongoing request must be distinguished from true failures, as they intentionally terminate the operation without populating response properties, often necessitating cleanup in the onabort handler.
Security and Access Controls
Same-Origin Policy Restrictions
The same-origin policy is a fundamental security mechanism implemented by web browsers to restrict interactions between web content from different origins. An origin is defined by the combination of a URL's protocol (such as HTTP or HTTPS), host (domain name), and port number (if specified); two resources share the same origin only if all three components match exactly.[36][37] For instance, requests from https://example.com:443 cannot access resources from http://example.com due to the protocol mismatch or from https://sub.example.com due to the differing host.
In the context of XMLHttpRequest, browsers enforce the same-origin policy by preventing cross-origin requests from being initiated or by blocking access to their responses. When a script attempts to send an XMLHttpRequest to a different origin without explicit permission (such as via CORS), the browser may allow the network transmission for simple methods like GET or POST but prohibits JavaScript from reading the response data, often throwing a security exception when attempting to access properties like responseText or responseXML.[2][36] This enforcement isolates potentially malicious scripts, ensuring they cannot exfiltrate sensitive data from other sites.
The same-origin policy originated in 1995 with the release of Netscape Navigator 2.0, coinciding with the introduction of JavaScript to prevent scripts from one document from accessing properties of another across origins.[38] It was applied to early implementations of XMLHttpRequest, first introduced by Microsoft in Internet Explorer 5 in 1999, to extend these protections to asynchronous HTTP requests and maintain consistent security boundaries as web applications evolved.[38]
This policy has significant implications for web applications: it safeguards against data exfiltration by blocking unauthorized cross-origin data access. While it limits attacker feedback in attacks like cross-site request forgery (CSRF) by preventing reading of responses, it does not prevent the sending of forged requests using user credentials; CSRF requires additional mitigations such as tokens.[38] However, it also limits legitimate use cases, such as mashups that aggregate content from multiple domains, by restricting seamless data integration unless workarounds are employed. An exception exists for subdomains, where the deprecated document.domain property can be set to a common parent domain (e.g., from sub.example.com to example.com) on both pages to relax the policy and enable cross-subdomain communication, though this must be mutually configured and does not override protocol or port differences. However, as of 2023, the setter for document.domain has been disabled in major browsers including Chrome and Edge, rendering this exception non-functional; alternatives like the postMessage API should be used for cross-subdomain communication.[36][38][39]
Developers can detect blocked cross-origin attempts with XMLHttpRequest when the readyState reaches 4 (indicating completion) but the status is 0, signaling a network error or policy violation rather than a valid HTTP response code.[36][2]
Enabling Cross-Origin Requests with CORS
Cross-Origin Resource Sharing (CORS) is a mechanism that enables web browsers to perform controlled cross-origin HTTP requests, including those made via XMLHttpRequest, by allowing servers to specify which origins are permitted to access their resources. Defined as a W3C Recommendation on January 16, 2014, CORS relies on a set of HTTP headers exchanged between the client and server to relax the same-origin policy restrictions that otherwise block such requests.[40] This standard facilitates secure data exchange in modern web applications, such as loading APIs from different domains, without compromising browser security.
For simple requests—such as GET or POST with standard headers and no custom content types—the browser sends the request directly, and the server responds with headers indicating permission. However, for non-simple requests, which include methods like PUT or DELETE, or those with custom headers or non-standard content types (e.g., application/json), the browser first issues a preflight request using the OPTIONS method. This preflight checks if the actual request is allowed by querying the server for supported methods, headers, and origins, preventing potentially unsafe operations from proceeding.[41][40] The preflight is transparent to JavaScript code using XMLHttpRequest, as the browser handles it automatically before the main request.
On the client side, developers can configure XMLHttpRequest to include credentials like cookies or HTTP authentication by setting the withCredentials property to true before sending the request. For example:
javascript
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/data');
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({key: 'value'}));
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/data');
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({key: 'value'}));
This enables the request to carry user credentials across origins, but it requires the server to explicitly allow credentials via additional headers. Without withCredentials, the request remains anonymous and excludes sensitive data. The XMLHttpRequest flow integrates seamlessly with CORS, where the browser enforces the policy and exposes the response only if the server approves.[25]
Server-side implementation is crucial for enabling CORS, requiring responses to include specific headers. The Access-Control-Allow-Origin header specifies permitted origins, such as * for any origin (not recommended with credentials) or a specific domain like https://example.com. For preflight responses, servers must also set Access-Control-Allow-Methods (e.g., GET, POST, PUT) and Access-Control-Allow-Headers (e.g., Content-Type, [Authorization](/page/Authorization)) to match the client's intent. If credentials are involved, the Access-Control-Allow-Credentials: true header is added, but this prohibits wildcard origins in Access-Control-Allow-Origin. For instance, a server might respond to a preflight with:
Access-Control-Allow-Origin: https://client.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://client.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
These headers must be present in both preflight and actual responses for the request to succeed.[40]
Despite its flexibility, CORS has limitations when used with XMLHttpRequest. Browsers block access to response data from cross-origin requests unless the server includes the necessary Access-Control-Allow-Origin header, preventing unauthorized reads even if the request is sent. Credentialed requests (with withCredentials: true) are further restricted: they cannot use the wildcard * for origins and must match exactly, enhancing security but requiring precise configuration. Additionally, CORS does not support reading certain response headers without explicit exposure via Access-Control-Expose-Headers, limiting what client code can inspect. These constraints ensure that cross-origin access remains opt-in and controlled by the resource owner.[40]
Alternatives to XMLHttpRequest
Introduction to the Fetch API
The Fetch API, defined in the WHATWG Fetch Standard, provides a modern interface for fetching resources across the network in web browsers, serving as a unified architecture for HTTP requests, responses, and related processes.[42] Introduced as part of the evolving web platform standards around 2015, it exposes a global fetch() function that initiates network requests and returns a Promise resolving to a Response object, enabling asynchronous handling without the event-driven complexity of earlier methods.[43] This standard ensures consistency in fetching behaviors across various web APIs, such as those used in HTML elements and JavaScript modules.[42]
At its core, the Fetch API uses the syntax fetch(resource, init), where the first argument is the URL or Request object to fetch, and the optional init parameter is an object configuring the request details.[44] Key options in init include method (e.g., 'GET' or 'POST'), headers (a Headers object for custom headers), body (data to send, such as FormData or JSON strings), credentials (to include cookies or not, like 'include' or 'same-origin'), and mode (controlling CORS behavior, such as 'cors' for cross-origin requests with restrictions).[45] These options allow developers to tailor requests flexibly while adhering to web security models.
Compared to XMLHttpRequest, the Fetch API offers several advantages, including native support for Promises, which integrates seamlessly with async/await for cleaner asynchronous code, and the ability to stream response bodies via ReadableStream for efficient handling of large payloads.[43] Error handling is simplified, as network failures reject the Promise, though HTTP errors (like 404) do not, avoiding the need to track states like readyState. Key differences include the absence of a synchronous mode—Fetch is strictly asynchronous—and built-in methods like response.json() for automatic JSON parsing, alongside support for AbortController and AbortSignal to cancel ongoing requests programmatically.[43]
Browser support for the Fetch API is robust in modern environments, with full implementation in Chrome 42 and later (released April 2015), Firefox 39 and later (July 2015), Safari 10.1 and later (March 2017), and Edge 14 and later (August 2016).[46] For older browsers, polyfills such as whatwg-fetch can provide compatibility by implementing the Promise-based interface.
The Fetch API and XMLHttpRequest (XHR) differ in their core capabilities, with Fetch providing a more modern, promise-based interface that supports advanced features like streaming responses via ReadableStream, enabling efficient handling of large payloads without buffering the entire response in memory.[43] In contrast, XHR natively supports progress events for both uploads (via XMLHttpRequestUpload) and downloads (via onprogress), allowing straightforward monitoring of request advancement, a feature that Fetch lacks in its basic form and requires custom implementation using streams for download progress or falling back to XHR for uploads.[47] Additionally, XHR includes built-in XML parsing through the responseXML property, returning a parsed Document object, whereas Fetch delivers raw Response objects that necessitate manual parsing for XML or other formats.
In terms of usability, Fetch mitigates the "callback hell" associated with XHR's event-driven model—where multiple onreadystatechange handlers must manage state changes—by leveraging native promises and async/await syntax for cleaner chaining and error handling via .catch().[45] This makes Fetch preferable for contemporary JavaScript development in ES6+ environments. However, XHR offers broader legacy browser support, functioning reliably in Internet Explorer 11 and earlier without polyfills, while Fetch requires shims like whatwg-fetch for such compatibility. Both APIs handle CORS identically, relying on server-side headers for cross-origin permissions, so migration does not alter security configurations.[48]
Performance between the two is generally comparable in terms of network latency, as both utilize the underlying HTTP stack, but subtle differences emerge in processing overhead. Benchmarks indicate that Fetch can be marginally faster for JSON parsing in some browsers due to its promise-based offloading, potentially reducing perceived latency by around 100ms for large payloads, though results vary: XHR outperforms Fetch in Chrome (7.29 ops/sec vs. 5.25 ops/sec for large JSON fetches) while Fetch leads in Firefox (9.99 ops/sec vs. 8.84 ops/sec).[49][50] XHR's upload progress tracking is unavailable in basic Fetch implementations, potentially impacting user experience in file upload scenarios without additional streaming logic.[47]
For migration, developers can replace XHR's open() and send() methods with fetch(url, { method: 'POST', body: data }), substituting onreadystatechange logic with .then(response => response.json()).then(data => process(data)).catch(error => handleError(error)) to maintain asynchronous flow.[45] XHR remains suitable for applications requiring precise upload monitoring or targeting pre-ES6 browsers like IE11, whereas Fetch is ideal for new projects benefiting from its extensibility, such as integration with Service Workers for request interception.[48][51]