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. Ajax
andXMLHttpRequest
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. XMLHttpRequest
The 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. XMLHttpRequest
compatibility
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 all
xhr
object - IE10/IE11 is partially supported but not supported
xhr.responseType
forjson
- Some browsers do not support request timeout, that is, they cannot be used
xhr.timeout
- Some browsers do not support it
xhr.responseType
forblob
4. DetailXMLHttpRequest
How 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 as
content-type
And we can also write thatContent-Type
And even written ascontent-Type
; Content-Type
The 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.setRequestHeader
Must be inopen()
After the method,send()
Method is called before, otherwise an error is thrown;setRequestHeader
It can be called multiple times, and the resulting value is not overriddenoverride
Instead, use the appendappend
The 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…
- use
getAllResponseHeaders()
All I seeresponse header
With actual consoleNetwork
See in theresponse header
Not the same - use
getResponseHeader()
To obtain aheader
Value 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 response
Set-Cookie
,Set-Cookie2
These 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.response
Data 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.response The data type |
instructions |
---|---|---|
"" |
String string |
Default value (no settingresponseType When) |
"text" |
String string |
|
"document" |
Document object |
Hope to return toXML When formatting data |
"json" |
javascript object |
Internet Explorer 10 and Internet Explorer 11 do not support compatibility problems |
"blob" |
Blob object |
|
"arrayBuffer" |
ArrayBuffer object |
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.responseType
About: whenresponseType
for""
or"text"
When the value is""
;responseType
Otherwise, the value isnull
- Default: empty string
-
xhr.responseText
- The default value is an empty string
""
- Only when the
responseType
为"text"
,""
When,xhr
Object, 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
- The default value is an empty string
-
xhr.responseXML
- The default value is
null
- Only when the
responseType
为"text"
,""
,"document"
When,xhr
Object, 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 is
null
: The request is incomplete, the request fails, the request succeeds but the returned data cannot be parsed correctly
- The default value is
4.5 How Can I Traceajax
Current 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 timexhr The 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: Onlyxhr In aOPENED State 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.response You 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:
- Can be found in
send()
Set this laterxhr.timeout
, but the timing start point is still the callxhr.send()
The moment of method. - when
xhr
As async
When synchronizing a request,xhr.timeout
Must be set to0
Otherwise, 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/HEADER
Etc. This parameter is case insensitiveurl
: Requested address, can be relative address such asexample.php
theThe relativeIs relative to the current web pageurl
The path; It can also be an absolute addresshttp://www.example.com/example.php
async
: The default value istrue
Is an asynchronous request, ifasync=false
Is 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.timeout
Must be0
xhr.withCredentials
Must befalse
xhr.responseType
Must 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 is
xhr.upload
The object’sonprogress
The event - The download triggers
xhr
The object’sonprogress
The 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:
- if
data
isDocument
Type, alsoHTML Document
The type,content-type
The default value istext/html; charset=UTF-8
; Otherwise, forapplication/xml; charset=UTF-8
; - if
data
isDOMString
Type,content-type
The default value istext/plain; charset=UTF-8
; - if
data
isFormData
Type,content-type
The default value ismultipart/form-data; boundary=[xxx]
- if
data
If the value is of other types, this parameter is not setcontent-type
The 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.withCredentials
withCORS
What 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. xhr
Related 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:
-
XMLHttpRequestEventTarget interface defines seven events:
onloadstart
onprogress
onabort
ontimeout
onerror
onload
onloadend
-
Each XMLHttpRequest has an upload property, and an upload is an XMLHttpRequestUpload object
-
XMLHttpRequest and XMLHttpRequestUpload have inherited the same XMLHttpRequestEventTarget interface, so the XHR and XHR. Upload are listed in the first seven events
-
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.readyState Trigger when changing; butxhr.readyState By the0 Values into0 When does not trigger. |
onloadstart |
callxhr.send() Method immediately after ifxhr.send() This event is not raised if it is not called. |
onprogress |
xhr.upload.onprogress During the upload phase (i.exhr.send() After that,xhr.readystate=2 Before) trigger, trigger every 50ms;xhr.onprogress During the download phase (i.exhr.readystate=3 Trigger 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.timeout Not equal to 0, i.e. from the beginning of the requestonloadstart I start counting, when I get toxhr.timeout The set time request is not finishedonloadend , this event is triggered. |
onerror |
During a request, ifNetwork error Triggers this event (if it occursNetwork error “, the upload is not finished, will be triggered firstxhr.upload.onerror And 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.statusCode is4xx Does not belong toNetwork error , so it will not triggeronerror Event, but will triggeronload Events. |
5.3 Event triggering Sequence
When all is well with the request, the related events are fired in the following order:
- The trigger
xhr.onreadystatechange
(Each time after thatreadyState
Triggers a change) - The trigger
xhr.onloadstart
// Upload phase starts: - The trigger
xhr.upload.onloadstart
- The trigger
xhr.upload.onprogress
- The trigger
xhr.upload.onload
- The trigger
xhr.upload.onloadend
// Upload ends, download begins: - The trigger
xhr.onprogress
- The trigger
xhr.onload
- The trigger
xhr.onloadend
5.3.1 happenabort
/timeout
/error
Exception handling
Abort /timeout/error exceptions may occur during a request. So what does XHR do next once these exceptions occur? Follow-up procedure:
-
Abort the request immediately upon abort or timeout or error
-
Set readyState to 4 and trigger the xhr. onreadyStatechange event
-
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
-
The xhr.onProgress event is emitted
-
The XHR.[onabort or onTimeout or onError] event is raised
-
The xhr. onloadEnd event is emitted
5.3.2 in whichxhr
Registration 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