XMLHttpRequest

XMLHttpRequest (XHR) objects are used to interact with servers. You can retrieve data from a URL without having to do a full page refresh. This enables a Web page to update just part of a page without disrupting what the user is doing. XMLHttpRequest is used heavily in AJAX programming.

Despite its name, XMLHttpRequest can be used to retrieve any type of data, not just XML.

If your communication needs to involve receiving event data or message data from a server, consider using server-sent events through the EventSource interface. For full-duplex communication, WebSockets may be a better choice.

This feature is available in Web Workers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# Constructor
XMLHttpRequest()

# Properties
XMLHttpRequest.onreadystatechange

XMLHttpRequest.readyState - Read only

XMLHttpRequest.response - Read only
# Returns an ArrayBuffer, Blob, Document, JavaScript object, or a DOMString, depending on the value of XMLHttpRequest.responseType, that contains the response entity body.

XMLHttpRequest.responseText - Read only
# Returns a DOMString that contains the response to the request as text, or null if the request was unsuccessful or has not yet been sent.

XMLHttpRequest.responseType
# Is an enumerated value that defines the response type.
# Possible values are the empty string (default), "arraybuffer", "blob", "document", "json", and "text".

XMLHttpRequest.responseURL - Read only
# Returns the serialized URL of the response or the empty string if the URL is null.

XMLHttpRequest.responseXML - Read only
# Returns a Document containing the response to the request, or null if the request was unsuccessful, has not yet been sent, or cannot be parsed as XML or HTML. Not available in workers.

XMLHttpRequest.status - Read only
# Returns an unsigned short with the status of the response of the request.

XMLHttpRequest.statusText - Read only
# Returns a DOMString containing the response string returned by the HTTP server. Unlike XMLHttpRequest.status, this includes the entire text of the response message ("200 OK", for example).

XMLHttpRequest.timeout
# Is an unsigned long representing the number of milliseconds a request can take before automatically being terminated.

XMLHttpRequestEventTarget.ontimeout
# Is an EventHandler that is called whenever the request times out.

XMLHttpRequest.upload - Read only
# Is an XMLHttpRequestUpload, representing the upload process.

XMLHttpRequest.withCredentials
# Is a Boolean that indicates whether or not cross-site Access-Control requests should be made using credentials such as cookies or authorization headers.

# Event handlers
onreadystatechange
onload
onerror
onprogress
etc

More recent browsers, including Firefox, also support listening to the XMLHttpRequest events via standard addEventListener() APIs in addition to setting on* properties to a handler function.

# Methods
XMLHttpRequest.abort()
# Aborts the request if it has already been sent.

XMLHttpRequest.getAllResponseHeaders()
# Returns all the response headers, separated by CRLF, as a string, or null if no response has been received.

XMLHttpRequest.getResponseHeader()
# Returns the string containing the text of the specified header, or null if either the response has not yet been received or the header doesn't exist in the response.

XMLHttpRequest.open()
# Initializes a request.

XMLHttpRequest.overrideMimeType()
# Overrides the MIME type returned by the server.

XMLHttpRequest.send()
# Sends the request. If the request is asynchronous (which is the default), this method returns as soon as the request is sent.

XMLHttpRequest.setRequestHeader()
# Sets the value of an HTTP request header. You must call setRequestHeader()after open(), but before send().

# Events
abort
# Fired when a request has been aborted, for example because the program called XMLHttpRequest.abort().
# Also available via the onabort property.

error
# Fired when the request encountered an error.
# Also available via the onerror property.

load
# Fired when an XMLHttpRequest transaction completes successfully.
# Also available via the onload property.

loadend
# Fired when a request has completed, whether successfully (after load) or unsuccessfully (after abort or error).
# Also available via the onloadend property.

loadstart
# Fired when a request has started to load data.
# Also available via the onloadstart property.

progress
# Fired periodically when a request receives more data.
# Also available via the onprogress property.

timeout
# Fired when progress is terminated due to preset time expiring.
# Also available via the ontimeout property.

Using XMLHttpRequest

1
2
3
4
5
6
7
8
function reqListener () {
console.log(this.responseText);
}

var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://www.example.org/example.txt");
oReq.send();

通过XMLHttpRequest生成的请求可以有两种方式来获取数据,异步模式或同步模式。

请求的类型是由这个XMLHttpRequest对象的open()方法的第三个参数async的值决定的。如果该参数的值为false,则该XMLHttpRequest请求以同步模式进行,否则该过程将以异步模式完成。
也就是说,默认是同步模式。

Do not use synchronous requests outside Web Workers.

Analyzing and manipulating the responseXML property

If you use XMLHttpRequest to get the content of a remote XML document, the responseXML property will be a DOM object containing a parsed XML document. This could prove difficult to manipulate and analyze. There are four primary ways of analyzing this XML document:

  1. Using XPath to address (or point to) parts of it.
  2. Manually Parsing and serializing XML to strings or objects.
  3. Using XMLSerializer to serialize DOM trees to strings or to files.
  4. RegExp can be used if you always know the content of the XML document beforehand. You might want to remove line breaks, if you use RegExp to scan with regard to line breaks. However, this method is a “last resort” since if the XML code changes slightly, the method will likely fail.

Processing a responseText property containing an HTML document

If you use XMLHttpRequest to get the content of a remote HTML webpage, the responseText property is a string containing the raw HTML. This could prove difficult to manipulate and analyze. There are three primary ways to analyze and parse this raw HTML string:

  1. Use the XMLHttpRequest.responseXML property as covered in the article HTML in XMLHttpRequest.
  2. Inject the content into the body of a document fragment via fragment.body.innerHTML and traverse the DOM of the fragment.
  3. RegExp can be used if you always know the content of the HTML responseText beforehand. You might want to remove line breaks, if you use RegExp to scan with regard to linebreaks. However, this method is a “last resort” since if the HTML code changes slightly, the method will likely fail.

Handling binary data

Although XMLHttpRequest is most commonly used to send and receive textual data, it can be used to send and receive binary content. There are several well tested methods for coercing the response of an XMLHttpRequest into sending binary data. These involve utilizing the overrideMimeType() method on the XMLHttpRequest object and is a workable solution.

1
2
3
4
5
var oReq = new XMLHttpRequest();
oReq.open("GET", url);
// retrieve data unprocessed as a binary string
oReq.overrideMimeType("text/plain; charset=x-user-defined");
/* ... */

However, more modern techniques are available, since the responseType attribute now supports a number of additional content types, which makes sending and receiving binary data much easier.

For example, consider this snippet, which uses the responseType of “arraybuffer“ to fetch the remote content into a ArrayBuffer object, which stores the raw binary data.

1
2
3
4
5
6
7
8
9
var oReq = new XMLHttpRequest();

oReq.onload = function(e) {
var arraybuffer = oReq.response; // not responseText
/* ... */
}
oReq.open("GET", url);
oReq.responseType = "arraybuffer";
oReq.send();

Monitoring progress

XMLHttpRequest provides the ability to listen to various events that can occur while the request is being processed. This includes periodic progress notifications, error notifications, and so forth.

Support for DOM progress event monitoring of XMLHttpRequest transfers follows the specification for progress events: these events implement the ProgressEvent interface. The actual events you can monitor to determine the state of an ongoing transfer are:

  • progress

    The amount of data that has been retrieved has changed.

  • load

    The transfer is complete; all data is now in the response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var oReq = new XMLHttpRequest();

oReq.addEventListener("progress", updateProgress);
oReq.addEventListener("load", transferComplete);
oReq.addEventListener("error", transferFailed);
oReq.addEventListener("abort", transferCanceled);
oReq.addEventListener("loadend", loadEnd);

oReq.open();

// ...

// progress on transfers from the server to the client (downloads)
function updateProgress (oEvent) {
if (oEvent.lengthComputable) {
var percentComplete = oEvent.loaded / oEvent.total * 100;
// ...
} else {
// Unable to compute progress information since the total size is unknown
}
}

function transferComplete(evt) {
console.log("The transfer is complete.");
}

function transferFailed(evt) {
console.log("An error occurred while transferring the file.");
}

function transferCanceled(evt) {
console.log("The transfer has been canceled by the user.");
}

function loadEnd(e) {
console.log("The transfer finished (although we don't know if it succeeded or not).");
}

Submitting forms and uploading files

Instances of XMLHttpRequest can be used to submit forms in two ways:

Using the FormData API is the simplest and fastest, but has the disadvantage that data collected can not be stringified.
Using only AJAX is more complex, but typically more flexible and powerful.

Using nothing but XMLHttpRequest

Submitting forms without the FormData API does not require other APIs for most use cases. The only case where you need an additional API is if you want to upload one or more files, where you use the FileReader API.

A brief introduction to the submit methods

An html

can be sent in four ways:

  • using the POST method and setting the enctype attribute to application/x-www-form-urlencoded (default);

    1
    2
    Content-Type: application/x-www-form-urlencoded
    foo=bar&baz=The+first+line.%0D%0AThe+second+line.%0D%0A
  • using the POST method and setting the enctype attribute to text/plain;

    1
    2
    3
    4
    5
    Content-Type: text/plain

    foo=bar
    baz=The first line.
    The second line.
  • using the POST method and setting the enctype attribute to multipart/form-data;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Content-Type: multipart/form-data; boundary=---------------------------314911788813839

    -----------------------------314911788813839
    Content-Disposition: form-data; name="foo"

    bar
    -----------------------------314911788813839
    Content-Disposition: form-data; name="baz"

    The first line.
    The second line.

    -----------------------------314911788813839--
    1
    2
    3
    4
    <form id="upload-form" action="yyy" method="post" enctype="multipart/form-data" >
    <input type="file" id="upload" name="upload" />
    <input type="submit" value="Upload" />
    </form>
  • using the GET method (in this case the enctype attribute will be ignored).

    1
    2
    # a string like the following will be added to the URL
    ?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.

Using FormData objects

The FormData constructor lets you compile a set of key/value pairs to send using XMLHttpRequest. Its primary use is in sending form data, but can also be used independently from a form in order to transmit user keyed data. The transmitted data is in the same format the form’s submit() method uses to send data, if the form’s encoding type were set to “multipart/form-data”. FormData objects can be utilized in a number of ways with an XMLHttpRequest. For examples, and explanations of how one can utilize FormData with XMLHttpRequests, see the Using FormData Objects page. For didactic purposes here is a *translation* of the previous example transformed to use the FormData API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<progress id="uploadprogress" min="0" max="100" value="0">0</progress>

form.on('submit',function() {
  
// 检查是否支持FormData
if(window.FormData) { 
  var formData = new FormData();

  // 建立一个upload表单项,值为上传的文件
  formData.append('upload', document.getElementById('upload').files[0]);
  var xhr = new XMLHttpRequest();
  xhr.open('POST', $(this).attr('action'));
  // 定义上传完成后的回调函数
  xhr.onload = function () {
    if (xhr.status === 200) {
      console.log('上传成功');
    } else {
      console.log('出错了');
    }
  };
  xhr.send(formData);
  
  xhr.upload.onprogress = function (event) {
  if (event.lengthComputable) {
    var complete = (event.loaded / event.total * 100 | 0);
    var progress = document.getElementById('uploadprogress');
    progress.value = progress.innerHTML = complete;
  }
};

}
});
1
2
3
4
5
6
7
8
9
10
11
12
// 检查浏览器是否支持拖放上传
if('draggable' in document.createElement('span')){
  var holder = document.getElementById('holder');
  holder.ondragover = function () { this.className = 'hover'; return false; };
  holder.ondragend = function () { this.className = ''; return false; };
  holder.ondrop = function (event) {
    event.preventDefault();
    this.className = '';
    var files = event.dataTransfer.files;
    // do something with files
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function loadListener (e) {
console.log(this.responseText);

var arrayBuffer = e.response;
if (arrayBuffer) {
var byteArray = new Uint8Array(arrayBuffer);
for (var i = 0; i < byteArray.byteLength; i++) {
// 对数组中的每个字节进行操作
}
}

var blob = new Blob([e.response], {type: "image/png"});

if(oReq.status === 200) {

}
}

var oReq = new XMLHttpRequest();
oReq.addEventListener("load", loadListener);
oReq.open("GET", "http://www.example.org/example.txt");

// 空字符串(默认), arraybuffer, blob, document, json, text
oReq.responseType = "arraybuffer";

oReq.addEventListener("progress", updateProgress);
oReq.addEventListener("load" , transferComplete);
oReq.addEventListener("error", transferFailed );
oReq.addEventListener("abort", transferCanceled);

oReq.send();

// 服务端到客户端的传输进程(下载)
function updateProgress (oEvent) {
if (oEvent.lengthComputable) {
var percentComplete = oEvent.loaded / oEvent.total * 100;
// ...
} else {
// 总大小未知时不能计算进程信息
}
}

function transferComplete(evt) {
console.log("The transfer is complete.");
}

function transferFailed(evt) {
console.log("An error occurred while transferring the file.");
}

function transferCanceled(evt) {
console.log("The transfer has been canceled by the user.");
}

Get last modified date

1
2
3
4
5
6
7
8
function getHeaderTime () {
console.log(this.getResponseHeader("Last-Modified")); /* A valid GMTString date or null */
}

var oReq = new XMLHttpRequest();
oReq.open("HEAD" /* use HEAD if you only need the headers! */, "yourpage.html");
oReq.onload = getHeaderTime;
oReq.send();

Do something when last modified date changes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function getHeaderTime () {
var nLastVisit = parseFloat(window.localStorage.getItem('lm_' + this.filepath));
var nLastModif = Date.parse(this.getResponseHeader("Last-Modified"));

if (isNaN(nLastVisit) || nLastModif > nLastVisit) {
window.localStorage.setItem('lm_' + this.filepath, Date.now());
isFinite(nLastVisit) && this.callback(nLastModif, nLastVisit);
}
}

function ifHasChanged(sURL, fCallback) {
var oReq = new XMLHttpRequest();
oReq.open("HEAD" /* use HEAD - we only need the headers! */, sURL);
oReq.callback = fCallback;
oReq.filepath = sURL;
oReq.onload = getHeaderTime;
oReq.send();
}

/* Let's test the file "yourpage.html"... */
ifHasChanged("yourpage.html", function (nModif, nVisit) {
console.log("The page '" + this.filepath + "' has been changed on " + (new Date(nModif)).toLocaleString() + "!");
});

Cross-site XMLHttpRequest

Modern browsers support cross-site requests by implementing the Cross-Origin Resource Sharing (CORS) standard. As long as the server is configured to allow requests from your web application’s origin, XMLHttpRequest will work. Otherwise, an INVALID_ACCESS_ERR exception is thrown.

Bypassing the cache

A cross-browser compatible approach to bypassing the cache is appending a timestamp to the URL, being sure to include a “?” or “&” as appropriate.

As the local cache is indexed by URL, this causes every request to be unique, thereby bypassing the cache.

You can automatically adjust URLs using the following code:

1
2
3
4
var oReq = new XMLHttpRequest();

oReq.open("GET", url + ((/\?/).test(url) ? "&" : "?") + (new Date()).getTime());
oReq.send(null);

Security

The recommended way to enable cross-site scripting is to use the Access-Control-Allow-Origin HTTP header in the response to the XMLHttpRequest.

HTML in XMLHttpRequest

To discourage the synchronous use of XMLHttpRequest, HTML support is not available in the synchronous mode. Also, HTML support is only available if the responseType property has been set to "document". This limitation avoids wasting time parsing HTML uselessly when legacy code uses XMLHttpRequest in the default mode to retrieve responseText for text/html resources. Also, this limitation avoids problems with legacy code that assumes that responseXML is null for HTTP error pages (which often have a text/html response body).

1
2
3
4
5
6
7
var xhr = new XMLHttpRequest();
xhr.onload = function() {
console.log(this.responseXML.title);
}
xhr.open("GET", "file.html", true);
xhr.responseType = "document";
xhr.send();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Handling HTML on older browsers
# Note: This solution is very expensive for the interpreter. Use it only when it is really necessary.

function getHTML (oXHR, sTargetId) {
var rOpen = new RegExp("<(?!\!)\\s*([^\\s>]+)[^>]*\\s+id\\=[\"\']" + sTargetId + "[\"\'][^>]*>" ,"i"),
sSrc = oXHR.responseText, aExec = rOpen.exec(sSrc);

return aExec ? (new RegExp("(?:(?:.(?!<\\s*" + aExec[1] + "[^>]*[>]))*.?<\\s*" + aExec[1] + "[^>]*[>](?:.(?!<\\s*\/\\s*" + aExec[1] + "\\s*>))*.?<\\s*\/\\s*" + aExec[1] + "\\s*>)*(?:.(?!<\\s*\/\\s*" + aExec[1] + "\\s*>))*.?", "i")).exec(sSrc.slice(sSrc.indexOf(aExec[0]) + aExec[0].length)) || "" : "";
}

var oReq = new XMLHttpRequest();
oReq.open("GET", "yourPage.html", true);
oReq.onload = function () { console.log(getHTML(this, "intro")); };
oReq.send(null);

Example: using a timeout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function loadFile(url, timeout, callback) {
var args = Array.prototype.slice.call(arguments, 3);
var xhr = new XMLHttpRequest();
xhr.ontimeout = function () {
console.error("The request for " + url + " timed out.");
};
xhr.onload = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
callback.apply(xhr, args);
} else {
console.error(xhr.statusText);
}
}
};
xhr.open("GET", url, true);
xhr.timeout = timeout;
xhr.send(null);
}

# usage:
function showMessage (message) {
console.log(message + this.responseText);
}
loadFile("message.txt", 2000, showMessage, "New message!\n");

参考链接:

  1. https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
  2. https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
  3. https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
  4. https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests