This article was originally published on the OpenReplay blog, translated and shared by InfoQ Chinese.
Ajax is the core technology behind most web applications, it allows pages to make asynchronous requests to web services, so data can be displayed without a page round trip to the server without refreshing the data.
The term Ajax is not a technology; rather, it refers to a method of loading server data from client-side scripts. Several options have been introduced over the years, and there are currently two main approaches, one or both of which are used by most JavaScript frameworks.
In this article, we’ll examine the pros and cons of early XMLHttpRequest and modern Fetch to determine which Ajax API is best for your application.
XMLHttpRequest
XMLHttpRequest first appeared in 1999 as a non-standard Internet Explorer 5.0 ActiveX component, developed by Microsoft to support browser-based versions of Outlook, XML was the most popular (or touted) data format at the time, XMLHttpRequest also Support for text and the yet-to-be-invented JSON.
Jesse James Garrett coined the term “AJAX” in his 2005 article “AJAX: A New Approach to Web Applications” when AJAX-based applications like Google Mail and Google Maps already existed, but the term motivated developers , and caused an explosion of smooth Web 2.0 experiences.
AJAX is short for “Asynchronous JavaScript and XML,” although, strictly speaking, developers don’t need to use asynchronous methods, JavaScript, or XML. We will now refer to the generic “Ajax” term to mean any client-side process that fetches data from the server and updates the DOM without refreshing the entire page.
XMLHttpRequest is supported by all major browsers and became an official web standard in 2006. Here’s a simple example that fetches data from your domain/service/endpoint and displays the JSON result as text in the console:
const xhr = new XMLHttpRequest(); xhr.open("GET", "/service"); // state change event xhr.onreadystatechange = () => { // is request complete? if (xhr.readyState !== 4) return; if (xhr.status === 200) { // request successful console.log(JSON.parse(xhr.responseText)); } else { // request not successful console.log("HTTP error", xhr.status, xhr.statusText); } }; // start request xhr.send();
The onreadystatechange callback function runs several times during the life of the request; the readyState property of the XMLHttpRequest object returns the current state:
-
0 (uninitialized) – the request is not initialized
-
1 (loading) – server connection established
-
2 (loaded) – request received
-
3 (interactive) – processing requests
-
4 (complete) – the request is complete, the response is ready
A few functions can do a lot before reaching state 4.
Fetch
Fetch is a modern promise-based Ajax request API that first appeared in 2015 and is supported in most browsers. It is not built on XMLHttpRequest and provides better consistency with a more concise syntax. The following Promise chain function is the same as the XMLHttpRequest example above:
fetch("/service", { method: "GET" }) .then((res) => res.json()) .then((json) => console.log(json)) .catch((err) => console.error("error:", err));
Or you can use async/await:
try { const res = await fetch("/service", { method: "GET" }), json = await res.json(); console.log(json); } catch (err) { console.error("error:", err); }
Fetch is cleaner, more concise, and is often used in service workers.
Open Source Session Replay
OpenReplay is an open source alternative to FullStory and LogRocket that provides full observability by replaying everything the user does on your application and showing the stack of actions for each issue. OpenReplay is self-hosted and has full control over your data.
Happy debugging! Modern front-end teams – start monitoring your web applications freely.
Round 1: Fetch wins
The Fetch API has several advantages over the old XMLHttpRequest in addition to a cleaner and more concise syntax.
Header, request and response objects
In the simple fetch() example above, a string is used to define the URL endpoint, or a configurable Request object can be passed, which provides a series of properties about the call:
const request = new Request("/service", { method: "POST" }); console.log(request.url); console.log(request.method); console.log(request.credentials); // FormData representation of body const fd = await request.formData(); // clone request const req2 = request.clone(); const res = await fetch(request);
The Response object provides similar access to all the details:
console.log(res.ok); // true/false console.log(res.status); // HTTP status console.log(res.url); const json = await res.json(); // parses body as JSON const text = await res.text(); // parses body as text const fd = await res.formData(); // FormData representation of body
The Headers object provides a simple interface to set headers in a request or get headers in a response:
// set request headers const headers = new Headers(); headers.set("X-Requested-With", "ajax"); headers.append("Content-Type", "text/xml"); const request = new Request("/service", { method: "POST", headers, }); const res = await fetch(request); // examine response headers console.log(res.headers.get("Content-Type"));
cache control
Managing caching in XMLHttpRequest is challenging, you may find it necessary to append a random query string value to bypass browser caching, the Fetch method has built-in support for caching in the second parameter init object:
const res = await fetch("/service", { method: "GET", cache: "default", });
The cache can be set to:
-
‘default’ – if there is a new (unexpired) match, the browser cache is used; if not, the browser makes a conditional request to check if the resource has changed, and issues a new one if necessary ask
-
‘no-store’ – bypass browser cache and network response will not update it
-
‘reload’ – bypass browser cache, but update it with network response
-
‘no-cache’ – like ‘default’, except a conditional request is always made
-
‘force-cache’ – use the cached version if possible, even if it is out of date
-
‘only-if-cached’ – same force-cache, except no network requests
cross domain control
Sharing resources across domains allows client script to make Ajax requests to another domain, provided that the server allows the origin domain in the Access-Control-Allow-Origin response header; if this parameter is not set, both fetch() and XMLHttpRequest will fail. However, Fetch provides a mode property that sets the ‘no-cors’ property in the init object of the second parameter.
const res = await fetch( 'https://anotherdomain.com/service', { method: 'GET', mode: 'no-cors' } );
This will return a response that cannot be read but can be used by other APIs. For example, you could use the Cache API to store an image, script, or CSS file for later use, perhaps from a Service Worker.
Credential Control
XMLHttpRequest always sends browser cookies, the Fetch API will not send cookies unless you explicitly set the credentials property in the second parameter init object.
const res = await fetch("/service", { method: "GET", credentials: "same-origin", });
credentials can be set to:
-
‘omit’ – exclude cookies and HTTP authentication items (default)
-
‘same-origin’ – contains credentials for requests to same-origin urls
-
‘include’ — include all requested credentials
Note that include was the default in earlier API implementations, and if your users are likely to be running older browsers, you’ll have to explicitly set the credentials property.
redirect control
By default, both fetch() and XMLHttpRequest follow server redirects. However, fetch() provides alternative options in the second parameter init object:
const res = await fetch("/service", { method: "GET", redirect: "follow", });
redirect can be set to:
-
‘follow’ – follow all redirects (default)
-
‘error’ – abort on redirection (reject)
-
‘manual’ — return a manually processed response
data flow
XMLHttpRequest reads the entire response into an in-memory buffer, but fetch() can stream request and response data, a new technology where streaming allows you to process smaller chunks of data when sending or receiving. For example, you can process information in a multi-megabyte file before it is fully downloaded, the following example converts an incoming (binary) chunk of data to text and outputs it to the console. On slower connections, you’ll see smaller chunks of data arrive over a longer period of time.
const response = await fetch("/service"), reader = response.body .pipeThrough(new TextDecoderStream()) .getReader(); while (true) { const { value, done } = await reader.read(); if (done) break; console.log(value); }
Server side support
Fetch is fully supported in Deno and Node 18, using the same API on the server and client helps reduce cognitive costs, and also offers the possibility of an isomorphic JavaScript library that runs anywhere.
Round 2: XMLHttpRequest wins
Despite its flaws, XMLHttpRequest has some tricks to outperform ajax Fetch().
progress support
We can monitor the progress of the request by attaching a handler to the progress event of the XMLHttpRequest object. This is especially useful when uploading large files such as photos:
const xhr = new XMLHttpRequest(); // progress event xhr.upload.onprogress = (p) => { console.log(Math.round((p.loaded / p.total) * 100) + "%"); };
The object passed by the event handler has three properties:
-
lengthComputable – set to true if progress is computable
-
total – the total work or content length of the message body
-
loaded – the amount of work or content completed so far
The Fetch API doesn’t provide any method to monitor upload progress.
Timeout support
The XMLHttpRequest object provides a timeout property that can be set to the number of milliseconds the request is allowed to run before the request is automatically terminated; if it times out, a timeout event is fired to handle:
const xhr = new XMLHttpRequest(); xhr.timeout = 5000; // 5-second maximum xhr.ontimeout = () => console.log("timeout");
A function can be encapsulated in fetch() to implement the timeout function:
function fetchTimeout(url, init, timeout = 5000) { return new Promise((resolve, reject) => { fetch(url, init).then(resolve).catch(reject); setTimeout(reject, timeout); }); }
Alternatively, you can use Promise.race():
Promise.race([ fetch("/service", { method: "GET" }), new Promise((resolve) => setTimeout(resolve, 5000)), ]).then((res) => console.log(res));
Neither method is easy to use, plus the request will continue to run in the background.
Discontinued support
A running request can be canceled via XMLHttpRequest’s abort() method, if necessary, by attaching an abort event:
const xhr = new XMLHttpRequest(); xhr.open("GET", "/service"); xhr.send(); // ... xhr.onabort = () => console.log("aborted"); xhr.abort();
You can abort a fetch(), but it’s not that straightforward and requires an AbortController object:
const controller = new AbortController(); fetch("/service", { method: "GET", signal: controller.signal, }) .then((res) => res.json()) .then((json) => console.log(json)) .catch((error) => console.error("Error:", error)); // abort request controller.abort();
When fetch() aborts, the catch() block executes.
More explicit failure detection
When developers use fetch() for the first time, it seems logical to assume that an HTTP error like 404 Not Found or 500 Internal Server error will trigger a Promise rejection and run the associated catch() block, but it is not : Promise resolves these responses successfully, rejections will only occur if the network does not respond or the request is interrupted.
fetch()’s Response object provides status and ok properties, but it’s not always necessary to check them explicitly, XMLHttpRequest is more explicit because a single callback function handles each result: you should see the stuatus check in every example .
Browser support
I hope you don’t have to support Internet Explorer or browser versions prior to 2015, but if so, XMLHttpRequest is your only option. XMLHttpRequest is also stable and the API is unlikely to be updated. Fetch is relatively new and still lacks several key features, and while updates are unlikely to break the code, you can expect some maintenance.
Which API should be used?
Most developers will use the newer Fetch API, which has a cleaner syntax and advantages over XMLHttpRequest; that is, many of these benefits have specific use cases, but they are not needed in most applications. There are only two cases where XMLHttpRequest is still essential:
-
You’re supporting very old browsers – the need for that will decline over time.
-
You need to show the upload progress bar. Fetch will be supported in the future, but it may take a few years.
Both options are interesting and worth learning about them in detail!
Original link:
https://blog.openreplay.com/ajax-battle-xmlhttprequest-vs-the-fetch-api
The text and pictures in this article are from InfoQ
This article is reprinted from https://www.techug.com/post/ajax-battle-xmlhttprequest-and-fetch-api.html
This site is for inclusion only, and the copyright belongs to the original author.