When you look at the headline, some of you might be thinking, “I’ve successfully sent many Ajax requests using XHR, and I’m pretty comfortable with the basics.” I was thinking the same thing until RECENTLY, when I had a lot of trouble using XHR, I suddenly realized that I didn’t know enough about XHR. I only knew the basics. I decided to take a closer look at XHR, but after reading a few blogs that didn’t satisfy me, I decided to take a closer look at the W3C XMLHttpRequest standard. After reading the standard, I felt as clear as I had ever felt before. This article is based on the W3C XMLHttpRequest standard and some practical validation.

1. AjaxandXMLHttpRequest

Ajax is often equated with XMLHttpRequest, but when you look at it, they are two concepts in different dimensions.

Here’s what I think is a pretty accurate explanation of Ajax: Ajax stands for Asynchronous JavaScript and XML. Ajax is a new technique for creating better, faster, and more interactive web applications with the help of XML, HTML, CSS, and Java Script.

AJAX is based on the following open standards:

  • Browser-based presentation using HTML and Cascading Style Sheets (CSS).
  • Data is stored in XML format and fetched from the server.
  • Behind-the-scenes data fetches using XMLHttpRequest objects in the browser.
  • JavaScript to make everything happen.

As you can see from the above explanation, Ajax is a technical solution, but it is not a new technology. It relies on existing CSS/HTML/Javascript, and the core dependency is the XMLHttpRequest object provided by the browser, which enables the browser to make HTTP requests and receive HTTP responses.

So I summarize the relationship in one sentence: We use an XMLHttpRequest object to send an Ajax request.

2. XMLHttpRequestThe development course of

XMLHttpRequest started out as an interface provided by Microsoft’s Browser. Later, many other browsers provided this interface. Later, the W3C standardized it and proposed the XMLHttpRequest standard. The XMLHttpRequest standard is divided into Level 1 and Level 2. XMLHttpRequest Level 1 has the following disadvantages:

  • Restricted by the same origin policy, cross-domain requests cannot be sent.
  • Can not send binary files (such as pictures, video, audio, etc.), can only send plain text data;
  • In the process of sending and obtaining data, the progress information cannot be obtained in real time, and only the completion can be judged.

XMLHttpRequest Level 2 improves Level 1 with the following features:

  • Cross-domain requests can be sent, if the server allows it.
  • Support for sending and receiving binary data;
  • New formData object to support sending formData;
  • When sending and obtaining data, progress information can be obtained;
  • You can set the timeout period for the request.

Of course, for a more detailed comparison, you can refer to this article by Ruan, in which there are specific code examples for the new functions.

3. XMLHttpRequestcompatibility

For browser compatibility with XHR, you Can directly check out the XMLHttpRequest compatibility result provided by the website “Can I Use”. A screenshot is provided below.

As can be seen from the figure:

  • IE8/IE9 and Opera Mini are not supported at allxhrobject
  • IE10/IE11 is partially supported but not supportedxhr.responseTypeforjson
  • Some browsers do not support request timeout, that is, they cannot be usedxhr.timeout
  • Some browsers do not support itxhr.responseTypeforblob

4. DetailXMLHttpRequestHow to use

Let’s start with a simple example of code that sends an Ajax request using XMLHttpRequest.

function sendAjax() {
  // Construct form data
  var formData = new FormData();
  formData.append('username'.'johndoe');
  formData.append('id'.123456);
  // Create an XHR object
  var xhr = new XMLHttpRequest();
  // Set the timeout period for XHR requests
  xhr.timeout = 3000;
  // Format the data to be returned in the response
  xhr.responseType = "text";
  // Create a POST request, asynchronously
  xhr.open('POST'.'/server'.true);
  // Register the related event callback handler
  xhr.onload = function(e) { 
    if(this.status == 200||this.status == 304){
        alert(this.responseText); }}; xhr.ontimeout =function(e) {... }; xhr.onerror =function(e) {... }; xhr.upload.onprogress =function(e) {... };// Send data
  xhr.send(formData);
}

Copy the code

Here is an example of sending form data using XHR, and the entire process can be referenced in the comments.


Next, I’ll introduce the basic use of XHR in the form of questions from the user’s perspective. I will give a detailed introduction to the knowledge points involved in each question, some of which may be ignored by you at ordinary times.

4.1 How Do I Set the Request Header

When sending an Ajax request (essentially an HTTP request), you may need to set request headers such as Content-Type, Connection, cookie, accept-xxx, and so on. XHR provides the setRequestHeader to allow us to modify the request header.

void setRequestHeader(DOMString header, DOMString value);

Note:

  • The first argument to the header method is case-insensitive, that is, it can be written ascontent-typeAnd we can also write thatContent-TypeAnd even written ascontent-Type;
  • Content-TypeThe default value of is dependent on the specific type of data to be sent. Refer to the “What kind of data can be sent” section of this article.
  • setRequestHeaderMust be inopen()After the method,send()Method is called before, otherwise an error is thrown;
  • setRequestHeaderIt can be called multiple times, and the resulting value is not overriddenoverrideInstead, use the appendappendThe way. Here is a sample code:
var client = new XMLHttpRequest();
client.open('GET'.'demo.cgi');
client.setRequestHeader('X-Test'.'one');
client.setRequestHeader('X-Test'.'two');
// In the final request header, "x-test" is one, two
client.send();
Copy the code

4.2 How do I Obtain a Response Header

XHR provides two methods to obtain the response header: getAllResponseHeaders and getResponseHeader. The former gets all header fields in response, the latter just gets the value of a specified header field. In addition, the getResponseHeader(header) header argument is case insensitive.

DOMString getAllResponseHeaders();

DOMString getResponseHeader(DOMString header);

These two methods may seem simple, but they are riddled with pitfalls.

Have you ever come across a pit down there? Anyway, I met…

  1. usegetAllResponseHeaders()All I seeresponse headerWith actual consoleNetworkSee in theresponse headerNot the same
  2. usegetResponseHeader()To obtain aheaderValue when the browser throws an errorRefused to get unsafe header "XXX"

It took some searching to find the answer on Stack Overflow.

  • Reason 1:The W3C XHR standard limits this, specifying that the client cannot get the responseSet-Cookie,Set-Cookie2These two fields, whether in the same domain or in the cross-domain request;
  • Reason 2:The W3C’S CORS standard also limits cross-domain requests, stating that for cross-domain requests, the client is allowed to obtain only the response Header field”simple response header“And”Access-Control-Expose-Headers“(See explanation of both nouns below).

“Simple response header” includes header fields: cache-Control, Content-language,Content-Type,Expires,Last-Modified,Pragma; “Access-control-expose-headers” : this is a field in the response header of “access-Control-expose-headers” for cross-domain requests. For same-domain requests, this field is not included in the response header. The header fields listed in this field are the ones that the server allows to be exposed to the client.

So getAllResponseHeaders() can only get header fields that are not restricted (that is, considered safe), not all of them; If the getResponseHeader(header) method is called, the header parameter must be a non-restricted header field. Otherwise, the parsing to get the unsafe header error will be Refused.

4.3 How to Specifyxhr.responseData type of

Sometimes we want xhr.response to return the data type we want. For example: the response returns a pure JSON string, but we expect xhr.response to return a js object. How do we do that? There are two methods to implement this: the overrideMimeType() method provided at level 1 and the xhr.responseType property provided at level 2.

4.3.1 xhr.overrideMimeType()

The overrideMimeType method is available with XHR Level 1, so browsers are compatible. What this method does is it overrides the Content-Type of response, so what’s the point of doing that? For example, if the server returns a document or XML document to the client and we want to get a DOM object through xhr.response, we can use xhr.overrideMimeType(‘text/ XML; Charset = utF-8 ‘).

As another usage scenario, we all know that XHR Level 1 does not support direct transfer of BLOB binary data, so what if we do want to transfer BLOBs? At that time, overrideMimeType method was used to solve this problem.

Here is an example of code to get an image file:

var xhr = new XMLHttpRequest();
// Get an image from the server
xhr.open('GET'.'/path/to/image.png'.true);

// This line is the key!
// The response data is parsed in plain text format and the character set is replaced by the user's own character set
xhr.overrideMimeType('text/plain; charset=x-user-defined');

xhr.onreadystatechange = function(e) {
  if (this.readyState == 4 && this.status == 200) {
    // Get the binary string corresponding to the image file via responseText
    var binStr = this.responseText;
    // Then figure out a way to restore byte by byte to binary data
    for (var i = 0, len = binStr.length; i < len; ++i) {
      var c = binStr.charCodeAt(i);
      //String.fromCharCode(c & 0xff);
      var byte = c & 0xff; }}}; xhr.send();Copy the code

In the code example XHR requests an image by changing the content-type of response to ‘text/plain; Charset =x-user-defined’, so that XHR parses the received BLOB data in plain text format. The end user receives the binary string from this. ResponseText and converts it into bloB data.

4.3.2 xhr.responseType

ResponseType is a new attribute added to XHR Level 2 that specifies the data type of xhr.response. There are some compatibility issues with this responseType. See XMLHttpRequest compatibility section in this article. ResponseType = responseType; responseType = responseType; responseType = responseType;

value xhr.responseThe data type instructions
"" Stringstring Default value (no settingresponseTypeWhen)
"text" Stringstring
"document" Documentobject Hope to return toXMLWhen formatting data
"json" javascriptobject Internet Explorer 10 and Internet Explorer 11 do not support compatibility problems
"blob" Blobobject
"arrayBuffer" ArrayBufferobject

Here is an example of code that also gets an image, much simpler to do with xhr.response than xhr.overridemimeType.

var xhr = new XMLHttpRequest();
xhr.open('GET'.'/path/to/image.png'.true);
// Xhr.responseType can be set to 'blob' or to 'arrayBuffer'
//xhr.responseType = 'arrayBuffer';
xhr.responseType = 'blob';

xhr.onload = function(e) {
  if (this.status == 200) {
    var blob = this.response; . }}; xhr.send();Copy the code

4.3.3 summary

Although in XHR Level 2, both exist together. Xhr.responsetype replaces xhr.overridemimeType () with xhr.responseType. Xhr.overridemimetype () does what xhr.responsetype does. So we can now get rid of xhr.overridemimeType () altogether.

4.4 How do I Obtain Response Data

XHR provides three properties to retrieve the data returned by the request: xhr.response, xhr.responseText, and xhr.responsexml

  • xhr.response

    • Default: empty string""
    • This property has the correct value only when the request completes
    • When the request is incomplete, the value of this property may be""ornull, specific andxhr.responseTypeAbout: whenresponseTypefor""or"text"When the value is"";responseTypeOtherwise, the value isnull
  • xhr.responseText

    • The default value is an empty string""
    • Only when theresponseType"text",""When,xhrObject, at which point this property can be calledxhr.responseText, otherwise throw wrong
    • Only if the request succeeds will the correct value be retrieved. The value is an empty string in both cases"": The request is not completed or failed
  • xhr.responseXML

    • The default value isnull
    • Only when theresponseType"text","","document"When,xhrObject, at which point this property can be calledxhr.responseXML, otherwise throw wrong
    • The correct value is retrieved only if the request is successful and the returned data is properly parsed. In the following three cases, the value isnull: The request is incomplete, the request fails, the request succeeds but the returned data cannot be parsed correctly

4.5 How Can I TraceajaxCurrent status of the request

After making an Ajax request, what do you do if you want to keep track of what state the request is currently in?

This can be traced using the xhr.readyState property. This property is read-only and has a total of five possible values for different phases of XHR. The xhr. onReadyStatechange event is triggered every time the value of xhr.readyState changes, and we can make relevant state judgments in this event.

  xhr.onreadystatechange = function () {
    switch(xhr.readyState){
      case 1://OPENED
        //do something
            break;
      case 2://HEADERS_RECEIVED
        //do something
        break;
      case 3://LOADING
        //do something
        break;
      case 4://DONE
        //do something
        break;
    }
Copy the code
value state describe
0 UNSENT(Initial state, not open) At this timexhrThe object was successfully constructed,open()Method has not been called yet
1 OPENED(Opened, not sent) open()Method was successfully called,send()Method has not been called yet. Note: OnlyxhrIn aOPENEDState before being calledxhr.setRequestHeader()andxhr.send()Otherwise, an error will be reported
2 HEADERS_RECEIVED(Response header obtained) send()The method has been called, and the response header and response status have been returned
3 LOADING(Downloading the response body) Response body (response entity body) Downloading, passed in this statexhr.responseYou may already have response data
4 DONE(The entire data transmission process is completed) The entire data transfer process ends, regardless of whether the request succeeds or fails

4.6 How Do I Set the Timeout Period for a Request

If the request is not successful after a long time, we usually terminate the request in order not to use up network resources for nothing. XMLHttpRequest provides the timeout attribute to allow you to set a timeout for the request.

Xhr. timeout Unit: milliseconds Default: 0, that is, timeout is not set

As many students know: From the beginning of the request, if the request is not finished after the timeout time (including success or failure), the onTimeout event will be triggered to actively end the request.

So when does a request start? — when the xhr.onloadStart event is triggered, which is when you call the xhr.send() method. Because xhr.open() creates a connection but doesn’t actually start the data transfer, xhr.send() actually starts the data transfer process. Xhr.onloadstart is triggered only if xhr.send() is called.

So when does the request end? — xhr.loadend when the event is triggered.

In addition, there are two other pitfalls to watch out for:

  1. Can be found insend()Set this laterxhr.timeout, but the timing start point is still the callxhr.send()The moment of method.
  2. whenxhrAs asyncWhen synchronizing a request,xhr.timeoutMust be set to0Otherwise, you will make a mistake. See how to send a Synchronous request in the section of this article.

4.7 How Do I Send a Synchronization Request

XHR makes asynchronous requests by default, but also supports synchronous requests (which should be avoided in practice). The async argument passed in xhr.open () determines whether the request is asynchronous or synchronous.

open(method, url [, async = true [, username = null [, password = null]]])

  • method: The manner in which a request is made, e.gGET/POST/HEADEREtc. This parameter is case insensitive
  • url: Requested address, can be relative address such asexample.phptheThe relativeIs relative to the current web pageurlThe path; It can also be an absolute addresshttp://www.example.com/example.php
  • async: The default value istrueIs an asynchronous request, ifasync=falseIs a synchronous request

Until I read up on the W3C XHR standard, I thought synchronous and asynchronous requests were just blocking and non-blocking, and that everything else should be the same. I was wrong.

The W3C XHR standard describes the open() method as follows:

Throws an “InvalidAccessError” exception if async is false, the JavaScript global environment is a document environment, and either the timeout attribute is not zero, the withCredentials attribute is true, or the responseType attribute is not the empty string.

As you can see from the above paragraph, when XHR is a synchronous request, there are the following restrictions:

  • xhr.timeoutMust be0
  • xhr.withCredentialsMust befalse
  • xhr.responseTypeMust be""Pay attention to"text"Nor is it allowed)

If any of the above restrictions are not met, an error will be thrown. For asynchronous requests, there are no restrictions on these parameter Settings.

I mentioned earlier that you should avoid using sync requests in your pages. Why? Because we cannot set the request timeout (xhr.timeout is 0, i.e., unlimited). Without limiting timeouts, it is possible that the synchronous request will remain pending and the server will not return a response, so that the entire page will remain blocked and unable to respond to other user interactions.

In addition, there is no mention in the standard of events firing on synchronous requests, but IN practice I have encountered instances where some events that should be firing do not. For example, in Chrome, when XHR is a synchronization request, the onReadyStatechange event is not triggered when xhr.upload. Onprogress and xhr. onProgress events are not triggered when xhr.readyState changes from 2 to 3.

4.8 How can I Obtain the Upload and download Progress

When uploading or downloading large files, it is common to display the current upload and download progress in real time. Progress can be shown in real time via the onprogress event, which is triggered every 50ms by default. Note that the upload and download processes trigger onProgress events for different objects:

  • The upload trigger isxhr.uploadThe object’sonprogressThe event
  • The download triggersxhrThe object’sonprogressThe event
xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;
function updateProgress(event) {
  if (event.lengthComputable) {
    varcompletedPercent = event.loaded / event.total; }}Copy the code

4.9 What types of data can be sent

void send(data);

Xhr.send (data) takes one of the following types of data:

  • ArrayBuffer
  • Blob
  • Document
  • DOMString
  • FormData
  • null

If it is a GET/HEAD request, the send() method generally passes no arguments or null. But even if you do pass in an argument, the argument is eventually ignored and data in xhr.send(data) is set to null.

The data type of the data parameter in xhr.send(data) affects the default value of the request header content-type:

  • ifdataisDocumentType, alsoHTML DocumentThe type,content-typeThe default value istext/html; charset=UTF-8; Otherwise, forapplication/xml; charset=UTF-8;
  • ifdataisDOMStringType,content-typeThe default value istext/plain; charset=UTF-8;
  • ifdataisFormDataType,content-typeThe default value ismultipart/form-data; boundary=[xxx]
  • ifdataIf the value is of other types, this parameter is not setcontent-typeThe default value of

Of course these are only the default values for content-type, but if you manually set the value of the content-type in xhr.setrequestheader (), the above defaults will be overridden.

Uncaught NetworkError: Failed to execute ‘send’ on ‘XMLHttpRequest’ if the xhr.send(data) method is invoked in the disconnected state. Once a program throws an error, it cannot proceed without a catch, so try-catch should be used when calling the xhr.send(data) method.

try{
    xhr.send(data)
  }catch(e) {
    //doSomething...
  };
Copy the code

4.10 xhr.withCredentialswithCORSWhat relation

As we all know, browsers automatically add cookies to the Request header when making a same-domain request. But have you ever encountered a situation where cookies are not automatically added to the Request header when a cross-domain request is sent? The reason for this problem is that by default, browsers cannot send any authentication information such as “cookies” and “HTTP authentication schemes” when sending cross-domain requests according to the CORS standard. Unless xhr.withCredentials is true (XHR objects have an attribute called withCredentials, which defaults to false).

So the root cause is that cookies are also authentication information, and in cross-domain requests, the client must manually set xhr.withCredentials=true. In addition, the server must Allow requests to carry authentication information (the response headers contain access-Control-allow-credentials :true). In this way, the browser automatically adds cookies to the Request headers.

In addition, it is important to note that once cross-domain request can carry authentication information, the server must not set access-Control-Allow-Origin to *, but must set it to the domain name of the request page.

5. xhrRelated events

5.1 Event Classification

There are so many xHR-related events that it can sometimes be confusing to remember. But when I looked at the actual code implementation, it was easier to figure it out. Here is a partial implementation of XMLHttpRequest:

interface XMLHttpRequestEventTarget : EventTarget {
  // event handlers
  attribute EventHandler onloadstart;
  attribute EventHandler onprogress;
  attribute EventHandler onabort;
  attribute EventHandler onerror;
  attribute EventHandler onload;
  attribute EventHandler ontimeout;
  attribute EventHandler onloadend;
};

interface XMLHttpRequestUpload : XMLHttpRequestEventTarget {

};

interface XMLHttpRequest : XMLHttpRequestEventTarget {
  // event handler
  attribute EventHandler onreadystatechange;
  readonly attribute XMLHttpRequestUpload upload;
};
Copy the code

From the code we can see:

  1. XMLHttpRequestEventTarget interface defines seven events:

    • onloadstart
    • onprogress
    • onabort
    • ontimeout
    • onerror
    • onload
    • onloadend
  2. Each XMLHttpRequest has an upload property, and an upload is an XMLHttpRequestUpload object

  3. XMLHttpRequest and XMLHttpRequestUpload have inherited the same XMLHttpRequestEventTarget interface, so the XHR and XHR. Upload are listed in the first seven events

  4. Onreadystatechange is an event unique to XMLHttpRequest

So this watch is very clear: a total of eight XHR related events: seven XMLHttpRequestEventTarget + 1 unique onreadystatechange events; The XHR. Upload only seven XMLHttpRequestEventTarget events.

5.2 Event Triggering Conditions

Here is a list of xHR-related event trigger conditions that I put together myself. The most important thing to note is the trigger condition for onError events.

The event The trigger condition
onreadystatechange Every timexhr.readyStateTrigger when changing; butxhr.readyStateBy the0Values into0When does not trigger.
onloadstart callxhr.send()Method immediately after ifxhr.send()This event is not raised if it is not called.
onprogress xhr.upload.onprogressDuring the upload phase (i.exhr.send()After that,xhr.readystate=2Before) trigger, trigger every 50ms;xhr.onprogressDuring the download phase (i.exhr.readystate=3Trigger every 50ms.
onload Triggered when the request completes successfullyxhr.readystate=4
onloadend Triggered when the request ends, either successfully or failed
onabort When callingxhr.abort()After the trigger
ontimeout xhr.timeoutNot equal to 0, i.e. from the beginning of the requestonloadstartI start counting, when I get toxhr.timeoutThe set time request is not finishedonloadend, this event is triggered.
onerror During a request, ifNetwork errorTriggers this event (if it occursNetwork error“, the upload is not finished, will be triggered firstxhr.upload.onerrorAnd then triggerxhr.onerror; In case ofNetwork error, the upload has finished, then only triggerxhr.onerror).Pay attention toThis event is triggered only when a network-level exception occurs. For application-level exceptions, such as those returned by the responsexhr.statusCodeis4xxDoes not belong toNetwork error, so it will not triggeronerrorEvent, but will triggeronloadEvents.

5.3 Event triggering Sequence

When all is well with the request, the related events are fired in the following order:

  1. The triggerxhr.onreadystatechange(Each time after thatreadyStateTriggers a change)
  2. The triggerxhr.onloadstart

    // Upload phase starts:
  3. The triggerxhr.upload.onloadstart
  4. The triggerxhr.upload.onprogress
  5. The triggerxhr.upload.onload
  6. The triggerxhr.upload.onloadend

    // Upload ends, download begins:
  7. The triggerxhr.onprogress
  8. The triggerxhr.onload
  9. The triggerxhr.onloadend

5.3.1 happenabort/timeout/errorException handling

Abort /timeout/error exceptions may occur during a request. So what does XHR do next once these exceptions occur? Follow-up procedure:

  1. Abort the request immediately upon abort or timeout or error

  2. Set readyState to 4 and trigger the xhr. onreadyStatechange event

  3. If the upload phase is not over, the following events are emitted in sequence:

    • xhr.upload.onprogress
    • Xhr.upload.[onabort or ontimeout or onerror]
    • xhr.upload.onloadend
  4. The xhr.onProgress event is emitted

  5. The XHR.[onabort or onTimeout or onError] event is raised

  6. The xhr. onloadEnd event is emitted

5.3.2 in whichxhrRegistration success callback in event?

From the events described above, you can see that xhr. onreadyStatechange and xhr.onload events are triggered if the XHR request is successful. So which event do we register the successful callback in? I prefer the xhr.onload event because xhr. onreadyStatechange is triggered every time xhr.readyState changes, not when xhr.readyState=4.

xhr.onload = function () {
    // If the request succeeds
    if(xhr.status == 200) {//do successCallback}}Copy the code

The sample code above is written in a common way: determine if the HTTP status code is 200, if so, consider the request successful, and execute a success callback. There are pitfalls, such as when the HTTP status code returned is 201 instead of 200, and the request is successful, but the success callback logic is not executed. Therefore, a more reliable way to determine whether the HTTP status code is 2xx or 304 is considered successful.

  xhr.onload = function () {
    // If the request succeeds
    if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {//do successCallback}}Copy the code