Ajax Back
Ajax is a cornerstone of high-performance JavaScript.
This chapter mainly discusses the fastest techniques for sending data to and receiving it from the server, as well as the most efficient formats for encoding data.
1. Data Transmission
There are several different ways of communicating with a server, while Ajax is one of them without unloading the current page.
1.1 Requesting Data
There are five general techniques for requesting data from a server:
- XMLHttpRequest (XHR)
- Dynamic script tag insertion
- iframes
- Comet
- Multipart XHR
1.1.1 XMLHttpRequest
By far the most common technique used, XMLHttpRequest allows you to asynchronously send and receive data.
var url = './data.php';
var params = [
'id=934875',
'limit=20'
];
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
/** get the response headers */
var responseHeaders = xhr.getAllResponseHeaders();
/** get the data */
var data = xhr.reponseText;
}
};
xhr.open('GET', url + '?' + params.join('&'), true);
/** set a request header */
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
/** send the request to the server */
xhr.send(null);
A readyState
of 4 indicates that the entire response has been received and is available for manipulation.
It's possible to interact with the server responses as it's still being transferred by listening for readyState
3. This is known as streaming, and it's a powerful tool for improving the performance of your data requests:
xhr.onreadystatechange = function () {
if (xhr.readyState === 3) {
/** some data has been received */
} else if (xhr.readyState === 4) {
/** all data has been received */
}
};
However, there are still some drawbacks about using XMLHttpRequest. Because of the high degree of control the XHR offers, browsers place some restrictions on it. You cannot use XHR to request data from a different domain, and older versions of IE do not give you access to readyState
3, which prevents streaming.
Then, when using XHR to request data, what is the best choice between GET and POST? For requests that don't change the server state and only pull back data (this is called an idempotent action (冪等動作)), use GET, because GET requests are cached to improve performance.
POST should be used to fetch data only when the length of the URL and the parameters are close to or exceed 2,048 characters, as the limitation of IE.
1.1.2 Dynamic script tag insertion
The power of using this technique is allowing you to fetch data from a crossed domain. Of course, it's just a hack.
var scriptElement = document.createElement('script');
script.src = 'http://any-domain.com/javascript/lib.js';
document.getElementsByTagName('head')[0].appendChild(scriptElement);
However, this way offers less control than XHR. You can only use GET, and you don't have access to the response headers or to the entire response as a string. Because it's being used as the source for a script tag, it must be an executable JavaScript source. Any data, regardless of the format, must be enclosed in a callback function. For example, how to transfer data with JSON format:
function jsonCallback(jsonString) {
var str = jsonString;
if (Object.prototype.toString.call(str) !== '[object String]') {
/** ensure that the jsonString is a string */
str = JSON.stringify(str);
}
var data = JSON.parse(str);
/** start to process the data */
/** ... */
}
var scriptElement = document.createElement('script');
script.src = 'http://any-domain.com/javascript/lib.js';
document.getElementsByTagName('head')[0].appendChild(scriptElement);
Then the file of lib.js
can be coded like this:
jsonCallback('{ "status": 1, "colors": [ "#fff", "#000", "#ff0000" ] }');
Despite these limitations, this technique can be extremely fast, even faster than XHR, because it's not treated as a string that must be further processed.
There is another important problem that you should pay more attention to when using this technique. Any code that you incorporate into your page using dynamic script tag insertion will have complete control over the page, like modifying any content, redirecting users to another site, or even track their actions and send the data to a third party. Therefore, be careful about importing code from an external source.
1.1.3 Multipart XHR
The newest of the techniques mentioned here, multipart XHR (MXHR) allows you to pass multiple resources from the server to the client-side using only one HTTP request. The basis of this technique is to packaging up the resources with separating by some agreed-upon strings.
Let's take an example to explain it. First, a request is made to the server for several image resources:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
splitImages(xhr.responseText);
}
};
Next, on the server side, the images are read and converted into strings:
$images = ['kitten.jpg', 'sunset.jpg', 'baby.jpg'];
foreach ($images as $image) {
$image_fh = fopen($image, 'r');
$image_data = fread($image_fh, filesize($image));
fclose($image_fh);
$payloads[] = base64_encode($image_data);
}
/** combine all these into a string with a character as separator */
echo implode(chr(1), $payloads);
Then, come back to the client side, we can split up this images data like this:
function splitImages(imageString) {
var imageData = imageString.split('\u0001');
var imageElement;
for (var i = 0, len = imageData.length; i < len; i++) {
imageElement = document.createElement('img');
imageElement.src = 'data:image/jpeg;base64,' + imageData[i];
document.getElementById('container').appendChild(imageElement);
}
}
Certainly, you can also take strings for JavaScript code, CSS styles, and images and convert them into resources the browser can use:
/** handle images with different mineType */
function handleImageData(data, mineType) {
var img = document.createElement('img');
img.src = 'data:' + mineType + ';base64,' + data;
return img;
}
/** handle CSS styles */
function handleCSS(data) {
var style = document.createElement('style');
style.type = 'text/css';
style.innerText = data;
document.getElementsByTagName('head')[0].appendChild(style);
}
/** handle JavaScript code */
function handleJavaScript(data) {
eval(data);
}
As MXHR responses grow larger, it becomes necessary to process each resource as it is received, rather than waiting for the entire response:
var xhr = new XMLHttpRequest();
var getLatestPacketInterval;
var lastLength = 0;
xhr.open('GET', 'rollup_images.php', true);
xhr.onreadystatechange = function () {
if (readyState === 3 && getLatestPacketInterval === null) {
/** start polling */
getLatestPacketInterval = setInterval(function () {
getLatestPacket();
}, 25);
}
if (readyState === 4) {
/** stop polling */
clearInterval(getLatestPacketInterval);
/** get the last packet */
getLatestPacket();
}
};
xhr.send(null);
function getLatestPacket() {
var length = xhr.responseText.length;
var packet = xhr.reponseText.substring(lastLength, length);
/** process packet and handle images when hitting the agreed-upon character */
processPacket(packet);
lastLength = length;
}
There are some drawbacks when using this technique, the biggest one of which is that none of the fetched resources is cached in the browser. Another downside is that older versions of IE do not support readyState
3 or base64 data setting for images.
Despite this downsides, MXHR has also significantly improved overall page performance by reducing HTTP requests, which is one of the most extreme bottlenecks in Ajax.
1.2 Sending Data
There are times when you don't care about retrieving data, and instead only want to send it to the server. In such a situation, there are two techniques that are widely used: XHR and beacons.
1.2.1 XMLHttpRequest
When sending data to the server side, we can also use onerror
to catch failures during the process:
var xhr = new XMLHttpRequest();
xhr.onerror = function () {
/** resend or any other handled operations */
};
When it comes to performance, it's faster to use GET to send data back to the server by using XHR. This is because, for small amounts of data, a GET request is sent to the server in a single packet. A POST, on the other hand, is sent in a minimum of two packets, one for the headers and another for the POST body.
1.2.2 Beacons
This technique is very similar to dynamic script insertion. JavaScript is used to create a new Image
object, with the src
property set to the URL of a script on your server. Data we want to send can be set as parameters following this URL.
var url = './status_tracker.php';
var params = [
'step=2',
'time=1248027312'
];
(new Image()).src = url + '?' + params.join('&');
Note that no img
element has to be created or inserted into the DOM.
Even though you can catch errors from the server-side, it is the most efficient way to send information back to the server. To work around this, you can use a smart way like listening for the Image
object's load
event, which will tell you if the server successfully received the data. Furthermore, you can also check the width and height of the image that the server returned for a state. For instance, a width of 1 can be represented for "success", while 2 for "try again".
var beacon = new Image();
beacon.src = url + '?' + params.join('&');
beacon.onload = function () {
if (this.width === 1) {
/** successfully */
} else if (this.width === 2) {
/** failed and try again */
}
};
beacon.onerror = function () {
/** failed and try again */
};
If you don't need to return data in your response, you should send a response code of 204 No Content and no message body. This will prevent the client from waiting for a message body that will never come.
2. Data Formats
When considering data transmission, you must take into several factors: feature set, compatibility, performance, and direction (retrieve or send data). When considering data formats, the only scale you need for comparison is speed.
In this section, each of the data formats will be compared based on the file size of the list, the speed of parsing it, and the ease with which it's formed on the server.
2.1 XML
When Ajax first becomes popular, XML was the data format of choice, which had many advantages: extreme interoperability (互用性, with excellent support on both the server-side and the client-side), strict formatting, and easy validation.
However, compared with other formats, XML is extremely verbose. To create a piece of data, you must require a lot of structure. In general, in order to parse XML, not only must you know the particulars of the structure ahead of time, but you must also know exactly how to pull apart of the structure and reassemble it into a JavaScript object.
To make it easier to parse XML, a more efficient approach is to encode data as an attribute of a tag.
2.1.1 XPath
XPath can be much faster than getElementsByTagName
when parsing an XML document. However, as it is not universally supported, you may have to write fallback code when using the older style of DOM traversal.
2.1.2 Response sizes and parse times
Format | Size | Download time | Parse time | Total load time |
---|---|---|---|---|
Verbose XML | 582,960 bytes | 999.4ms | 434.1ms | 1342.5ms |
Simple XML | 437,960 bytes | 475.1ms | 83.1ms | 558.2ms |
Even though simple XML is faster than the verbose one, it's still slower than any other faster format. What it means is that XML has no place in high-performance Ajax, unless this is the only format of data.
2.2 JSON
JSON is a lightweight and easily-to-parse data format written using JavaScript object and array literal syntax. JSON data is executable JavaScript code, which can be parsed with using eavl()
or JSON.parse()
.
Using
eval()
in your code is dangerous, especially when using it to evaluate third-party JSON data. Whenever possible, use theJSON.parse()
to parse a JSON string natively.
2.2.1 JSON-P
When XHR is used, JSON data is returned as a string, which can be evaluated by using eval()
or JSON.parse()
. However, when using dynamic script tag insertion, JSON data is treated as just another JavaScript file and executed as native code. In order to accomplish this, the data must be wrapped in a callback function, a.k.a JSON with padding or JSON-P.
According to some tests, the fastest JSON format is JSON-P formed using arrays. But what you should take in mind is that do not encode any sensitive data in JSON-P, as it can be called by anyone with using dynamic script tag insertion.
2.2.2 Should you use JSON?
JSON is a cornerstone of high-performance Ajax, especially when it's used with dynamic script insertion. Lower size of the structure, with lower parse time.
2.3 HTML
Often the data you are requesting will be turned in to HTML for display on the page. One technique is forming all of the HTML on the server and then passing it to the client, where it can be dropped in the place with innerHTML
by JavaScript.
The problem with this technique is that HTML is a verbose data format, which is even more than XML. Therefore, you should use this technique only when the client-side CPU is more limited than bandwidth.
2.4 Custom Formatting
The ideal data format is one that includes just enough structure to allow you to separate individual fields from each other, like using a separator. One of the most important decisions when using this way is what to use as the separators.
In most cases, Los-number ASCII characters work well and are easy to represent in most server-side language.
In JavaScript, we can separate such string like this:
/** Regular Expression Delimiter */
var rows = xhr.responseText.split(/\u0001/);
/** String Delimiter (safer in older version of browsers) */
var rows = xhr.responseText.split('\u0001');
If you want to receive huge amounts of data on the client-side, this will be your best choice.
3. Ajax Performance Guidelines
Once you have selected the most appropriate transmission technique and data format depending on requirements, you can start to consider other optimization techniques.
3.1 Cache Data
To prevent an unnecessary request, there are two main ways:
- On the server-side, set HTTP headers that ensure the response will be cached in the browser.
- On the client-side, store fetched data locally so that it does not have to be requested again.
3.1.1 Setting HTTP headers
The Expires
header tells the browser how long a response can be cached, the value of which should be a date:
Expires: Mon, 28 Jul 2014 23:30:00 GMT
The server-side can set up such a header to clearly tell the browser to cache responses for a certain time.
3.1.2 Storing data locally
Instead o relying on the browser to handle caching, you can also do it in a more manual fashion by strong the responses locally. For example, store them with URL as a key into a JavaScript object, so that you can reuse them without requesting again.
4. Limitations
All JavaScript libraries have given us access to an Ajax object, which normalizes the differences between browsers and gives us a consistent interface so that we can focus more on our projects rather than the details of how XHR works in different browsers. However, it also means that we cannot access the full power of XMLHttpRequest.
To use XMLHttpRequest object without worrying about the problem of compatibility, here we will encapsulate a function to return a more powerful XMLHttpRequest object, which we can then interact with directly:
function createXHR() {
var msXMLProgId = [
'MSXML2.XMLHTTP.6.0', 'MSXML3.XMLHTTP', 'Microsoft.XMLHTTP', /** Doesn't support readyState 3. */ 'MSXML2.XMLHTTP.3.0' /** Doesn't support readyState 3. */
];
var xhr;
try {
xhr = new XMLHttpRequest(); /** try the standard way at first */
} catch (e) {
for (var i = 0, len = msXMLProgId.length; i < len; i++) {
try {
xhr = new ActiveXObject(msXMLProgId[i]);
} catch (e2) {}
}
} finally {
return xhr;
}
}
5. Summary
High-performance Ajax consists of knowing the specific requirements of your situation and selecting the correct data format and transmission technique to match.
As data formats, we should consider that:
- Plain text and HTML are highly situational, but they can save CPU cycles on the client-side.
- XML is widely available and supported, but it's verbose and slow to parse.
- JSON is lightweight and quick to parse.
- If you want to transfer huge amounts of data, it's best to use custom formats, designed by both the client and the server-side.
When requesting data:
- XHR gives you the most control and flexibility, even though it treats data as a string, slowing down the parse time.
- Dynamic script tag insertion allows for cross-domain request and native execution of JavaScript and JSON, though you cannot control more.
- Multipart XHR can be used to reduce the number of requests, though it does not cache any received resource.
When sending data:
- image beacons are a simple and efficient approach.
- XHR can also be used to send large amounts of data in POST.
There're several guidelines for improving Ajax:
- Reduce the number of requests you make, either by concatenating files or by using MXHR.
- Using Ajax to improve the loading time of pages.
As the plugin is integrated with a code management system like GitLab or GitHub, you may have to auth with your account before leaving comments around this article.
Notice: This plugin has used Cookie to store your token with an expiration.