XMLHttpRequest object

Introduction

The HTTP protocol is used for communication between the browser and the server. The user types a web address in the browser address bar, or submits content to the server through a web form, then the browser sends an HTTP request to the server.

In 1999, Microsoft released IE browser version 5.0, introducing a new feature for the first time: allowing JavaScript scripts to initiate HTTP requests to the server. This feature did not attract attention at the time, and it was not until the release of Gmail in 2004 and Google Map in 2005 that it attracted widespread attention. In February 2005, the term AJAX was formally proposed for the first time. It is an abbreviation of Asynchronous JavaScript and XML, which refers to asynchronous communication through JavaScript, to obtain XML documents from the server to extract data, and then update the corresponding part of the current web page. No need to refresh the entire web page. Later, the word AJAX became synonymous with JavaScript scripts initiating HTTP communication, that is to say, as long as the script initiates communication, it can be called AJAX communication. W3C also released its international standards in 2006.

Specifically, AJAX includes the following steps.

  1. Create an instance of XMLHttpRequest
  2. Make an HTTP request
  3. Receive the data returned by the server
  4. Update web page data

To sum it up, in one sentence, AJAX sends HTTP requests through the native XMLHttpRequest object, and processes the data after obtaining the data returned by the server. Now, the server returns all data in JSON format. The XML format is obsolete, but the name AJAX has become a generic term and its literal meaning has disappeared.

The XMLHttpRequest object is the main interface of AJAX, used for communication between the browser and the server. Although there are XML and Http in the name, it can actually use multiple protocols (such as file or ftp) to send data in any format (including string and binary).

XMLHttpRequest itself is a constructor, you can use the new command to generate an instance. It has no parameters.

var xhr = new XMLHttpRequest();

Once a new instance is created, you can use the open() method to specify some details of establishing an HTTP connection.

xhr.open("GET", "http://www.example.com/page.php", true);

The above code specifies the use of the GET method to establish a connection with the specified server URL. The third parameter true indicates that the request is asynchronous.

Then, specify a callback function to monitor changes in the communication state (readyState property).

xhr.onreadystatechange = handleStateChange;

function handleStateChange() {
  // ...
}

In the above code, once the state of the XMLHttpRequest instance changes, the listener function handleStateChange will be called

Finally, use the send() method to actually make the request.

xhr.send(null);

In the above code, the parameter of send() is null, which means there is no data body when sending the request. If you are sending a POST request, you need to specify the data body here.

Once the data returned by the server is obtained, AJAX will not refresh the entire webpage, but only update the relevant parts of the webpage, so as not to interrupt what the user is doing.

Note that AJAX can only send HTTP requests to the same source URL (protocol, domain name, and port are the same). If a cross-domain request is sent, an error will be reported (see the "Same Origin Policy" and "CORS Communication" chapters for details).

Below is a complete example of simple usage of the XMLHttpRequest object.

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
  // When the communication is successful, the status value is 4
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      console.log(xhr.responseText);
    } else {
      console.error(xhr.statusText);
    }
  }
};

xhr.onerror = function (e) {
  console.error(xhr.statusText);
};

xhr.open("GET", "/endpoint", true);
xhr.send(null);

Instance attributes of XMLHttpRequest

XMLHttpRequest.readyState

XMLHttpRequest.readyState returns an integer that represents the current state of the instance object. This attribute is read-only. It may return the following values.

-0, it means that the XMLHttpRequest instance has been generated, but the open() method of the instance has not been called yet. -1. It means that the open() method has been called, but the instance's send() method has not been called yet. You can still use the instance's setRequestHeader() method to set the header information of the HTTP request. -2. It means that the send() method of the instance has been called, and the header information and status code returned by the server have been received. -3, which means that the data body (body part) from the server is being received. At this time, if the responseType property of the instance is equal to text or an empty string, the responseText property will contain part of the information that has been received. -4, it means that the data returned by the server has been completely received, or this reception has failed.

During the communication process, whenever the state of the instance object changes, the value of its readyState property will change. Every time this value changes, the readyStateChange event will be triggered.

var xhr = new XMLHttpRequest();

if (xhr.readyState === 4) {
  // End of the request, process the data returned by the server
} else {
  // Display the prompt "Loading..."
}

In the above code, when xhr.readyState is equal to 4, it indicates that the HTTP request sent by the script has been completed. In other cases, it means that the HTTP request is still in progress.

XMLHttpRequest.onreadystatechange

The XMLHttpRequest.onreadystatechange property points to a listener function. This property will be executed when the readystatechange event occurs (the instance's readyState property changes).

In addition, if you use the abort() method of the instance to terminate the XMLHttpRequest request, it will also cause the readyState property to change, and cause the XMLHttpRequest.onreadystatechange property to be called.

Below is an example.

var xhr = new XMLHttpRequest();
xhr.open("GET", "http://example.com", true);
xhr.onreadystatechange = function () {
  if (xhr.readyState !== 4 || xhr.status !== 200) {
    return;
  }
  console.log(xhr.responseText);
};
xhr.send();

XMLHttpRequest.response

The XMLHttpRequest.response property represents the body of the data returned by the server (ie the body part of the HTTP response). It may be any data type, such as string, object, binary object, etc. The specific type is determined by the XMLHttpRequest.responseType property. This attribute is read-only.

If the request is not successful or the data is incomplete, this attribute is equal to null. However, if the responseType attribute is equal to text or an empty string, before the request is over (the stage where readyState is equal to 3), the response attribute contains part of the data that the server has returned.

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {
    handler(xhr.response);
  }
};

XMLHttpRequest.responseType

The XMLHttpRequest.responseType property is a string that indicates the type of data returned by the server. This property is writable. You can set the value of this property after calling the open() method and before calling the send() method to tell the browser how to interpret the returned data. If responseType is set to an empty string, it is equivalent to the default value of text.

The XMLHttpRequest.responseType property can be equal to the following values.

-"" (empty string): equivalent to text, indicating that the server returns text data. -"arraybuffer": ArrayBuffer object, which means that the server returns a binary array. -"blob": Blob object, which means that the server returns a binary object. -"document": Document object, which means that the server returns a document object. -"json": JSON object. -"text": String.

Among the above types, the text type is suitable for most situations, and it is more convenient to process text directly. The document type is suitable for returning HTML / XML documents. This means that for those websites that open CORS, you can directly use Ajax to grab web pages, and then directly perform DOM operations on the retrieved data without parsing HTML strings. The blob type is suitable for reading binary data, such as image files.

var xhr = new XMLHttpRequest();
xhr.open("GET", "/path/to/image.png", true);
xhr.responseType = "blob";

xhr.onload = function (e) {
  if (this.status === 200) {
    var blob = new Blob([xhr.response], { type: "image/png" });
    // or
    var blob = xhr.response;
  }
};

xhr.send();

If you set this property to ArrayBuffer, you can process binary data as an array.

var xhr = new XMLHttpRequest();
xhr.open("GET", "/path/to/image.png", true);
xhr.responseType = "arraybuffer";

xhr.onload = function (e) {
  var uInt8Array = new Uint8Array(this.response);
  for (var i = 0, len = uInt8Array.length; i < len; ++i) {
    // var byte = uInt8Array [i];
  }
};

xhr.send();

If you set this property to json, the browser will automatically call the JSON.parse() method on the returned data. In other words, what you get from the xhr.response property (note, not the xhr.responseText property) is not a text, but a JSON object.

XMLHttpRequest.responseText

The XMLHttpRequest.responseText property returns the string received from the server. This property is read-only. This attribute will contain complete data only after the HTTP request is received.

var xhr = new XMLHttpRequest();
xhr.open("GET", "/server", true);

xhr.responseType = "text";
xhr.onload = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseText);
  }
};

xhr.send(null);

XMLHttpRequest.responseXML

The XMLHttpRequest.responseXML property returns the HTML or XML document object received from the server. This property is read-only. If this request is not successful, or the received data cannot be parsed as XML or HTML, this attribute is equal to null.

The premise for this attribute to take effect is that the Content-Type header of the HTTP response is equal to text/xml or application/xml. This requires the XMLHttpRequest.responseType property to be set to document before sending the request. If the Content-Type header information of the HTTP response is not equal to text/xml and application/xml, but you want to get data from responseXML (that is, to parse the data according to the DOM format), then you need to manually call XMLHttpRequest .overrideMimeType() method to force XML parsing.

The data obtained by this attribute is the DOM tree of the document directly parsed.

var xhr = new XMLHttpRequest();
xhr.open("GET", "/server", true);

xhr.responseType = "document";
xhr.overrideMimeType("text/xml");

xhr.onload = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseXML);
  }
};

xhr.send(null);

XMLHttpRequest.responseURL

The XMLHttpRequest.responseURL attribute is a string that represents the URL of the server that sends the data.

var xhr = new XMLHttpRequest();
xhr.open("GET", "http://example.com/test", true);
xhr.onload = function () {
  // return http://example.com/test
  console.log(xhr.responseURL);
};
xhr.send(null);

Note that the value of this attribute is not necessarily the same as the request URL specified by the open() method. If there is a jump on the server side, this attribute returns the last URL that actually returned the data. In addition, if the original URL includes an anchor point (fragment), this attribute will strip the anchor point.

XMLHttpRequest.status,XMLHttpRequest.statusText

The XMLHttpRequest.status property returns an integer that represents the HTTP status code that the server responded to. Generally speaking, if the communication is successful, the status code is 200; if the server does not return a status code, the attribute defaults to 200. Before the request is sent, this attribute is 0. This attribute is read-only.

-200, OK, access is normal -301, Moved Permanently -302, Moved temporarily -304, Not Modified -307, Temporary Redirect -401, Unauthorized, unauthorized -403, Forbidden, access is forbidden -404, Not Found, the specified URL was not found -500, Internal Server Error, an error occurred on the server

Basically, there are only 2xx and 304 status codes, indicating that the server returns a normal status.

if (xhr.readyState === 4) {
  if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
    // Processing the server's return data
  } else {
    // error
  }
}

The XMLHttpRequest.statusText property returns a string representing the status prompt sent by the server. Unlike the status attribute, this attribute contains the entire status information, such as "OK" and "Not Found". Before the request is sent (that is, before the open() method is called), the value of this attribute is an empty string; if the server does not return a status prompt, the value of this attribute defaults to "OK". This attribute is read-only.

XMLHttpRequest.timeout,XMLHttpRequestEventTarget.ontimeout

The XMLHttpRequest.timeout property returns an integer indicating how many milliseconds later, if the request still has no results, it will be automatically terminated. If this attribute is equal to 0, it means that there is no time limit.

The XMLHttpRequestEventTarget.ontimeout property is used to set a listener function, if a timeout event occurs, the listener function will be executed.

Below is an example.

var xhr = new XMLHttpRequest();
var url = "/ server";

xhr.ontimeout = function () {
  console.error("The request for " + url + " timed out.");
};

xhr.onload = function () {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      // Process the data returned by the server
    } else {
      console.error(xhr.statusText);
    }
  }
};

xhr.open("GET", url, true);
// Specify a timeout of 10 seconds
xhr.timeout = 10 * 1000;
xhr.send(null);

Event monitoring properties

The XMLHttpRequest object can specify listener functions for the following events.

-XMLHttpRequest.onloadstart: listener function for loadstart event (HTTP request issued) -XMLHttpRequest.onprogress: monitor function for progress event (data is being sent and loaded) -XMLHttpRequest.onabort: Abort event (the request is aborted, such as the user calling the abort() method) listener function -XMLHttpRequest.onerror: listener function for error event (request failed) -XMLHttpRequest.onload: listener function for load event (request completed successfully) -XMLHttpRequest.ontimeout: listener function for timeout event (the time limit specified by the user has exceeded and the request has not been completed) -XMLHttpRequest.onloadend: listener function for loadend event (request completed, regardless of success or failure)

Below is an example.

xhr.onload = function () {
  var responseText = xhr.responseText;
  console.log(responseText);
  // process the response.
};

xhr.onabort = function () {
  console.log("The request was aborted");
};

xhr.onprogress = function (event) {
  console.log(event.loaded);
  console.log(event.total);
};

xhr.onerror = function () {
  console.log("There was an error!");
};

The listener function of the progress event has an event object parameter, which has three properties: the loaded property returns the amount of data that has been transmitted, the total property returns the total amount of data, and the lengthComputable property returns a boolean value, which means Whether the loading progress can be calculated. Among all these listening functions, only the listening function of the progress event has parameters, and none of the other functions.

Note that if a network error occurs (for example, the server cannot be connected), the error message cannot be obtained from the onerror event. In other words, there may be no error object, so only an error message can be displayed.

XMLHttpRequest.withCredentials

The XMLHttpRequest.withCredentials attribute is a boolean value, which indicates whether user information (such as cookies and HTTP header information for authentication) will be included in the request when a cross-domain request is made. The default is false, which means to example.com When making a cross-domain request, the cookie (if any) set by example.com on the machine will not be sent.

If you need to send cookies in cross-domain AJAX requests, you need to set the withCredentials property to true. Note that requests from the same origin do not need to set this attribute.

var xhr = new XMLHttpRequest();
xhr.open("GET", "http://example.com/", true);
xhr.withCredentials = true;
xhr.send(null);

In order for this property to take effect, the server must explicitly return the header information Access-Control-Allow-Credentials.

Access-Control-Allow-Credentials: true

If the withCredentials property is turned on, the cross-domain request will not only send cookies, but also set the cookies specified by the remote host. The opposite is also true. If the withCredentials property is not turned on, then the browser will ignore the cross-domain AJAX request even if it explicitly requires the browser to set cookies.

Note that scripts always follow the same-origin policy and cannot read cross-domain cookies from document.cookie or HTTP response header information. The withCredentials attribute does not affect this.

XMLHttpRequest.upload

XMLHttpRequest can not only send requests, but also send files, which is AJAX file upload. After sending the file, you can get an object through the XMLHttpRequest.upload property. By observing this object, you can know the progress of the upload. The main method is to monitor various events of this object: loadstart, loadend, load, abort, error, progress, timeout.

Suppose there is a <progress> element on the web page.

<progress min="0" max="100" value="0">0% complete</progress>

When a file is uploaded, specify the listener function of the progress event to the upload property to get the upload progress.

function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open("POST", "/server", true);
  xhr.onload = function (e) {};

  var progressBar = document.querySelector("progress");
  xhr.upload.onprogress = function (e) {
    if (e.lengthComputable) {
      progressBar.value = (e.loaded / e.total) * 100;
      // Compatible with old browsers that do not support the <progress> element
      progressBar.textContent = progressBar.value;
    }
  };

  xhr.send(blobOrFile);
}

upload(new Blob(["hello world"], { type: "text/plain" }));

Example methods of XMLHttpRequest

XMLHttpRequest.open()

The XMLHttpRequest.open() method is used to specify the parameters of the HTTP request, or to initialize the XMLHttpRequest instance object. It can accept five parameters in total.

void open(
   string method,
   string url,
   optional boolean async,
   optional string user,
   optional string password
);

-method: Represents the HTTP verb method, such as GET, POST, PUT, DELETE, HEAD, etc. -url: Indicates the URL to which the request is sent. -async: Boolean value, indicating whether the request is asynchronous, the default is true. If it is set to false, the send() method will not proceed to the next step until it receives the result from the server. This parameter is optional. Because synchronous AJAX requests will cause the browser to lose response, many browsers have forbidden to use it in the main thread, and only allow it to be used in Worker. Therefore, this parameter should not easily be set to false. -user: indicates the user name used for authentication, the default is an empty string. This parameter is optional. -password: indicates the password used for authentication, the default is an empty string. This parameter is optional.

Note that if you use this method again for an AJAX request that has used the open() method, it is equivalent to calling abort(), which means terminating the request.

Below is an example of sending a POST request.

var xhr = new XMLHttpRequest();
xhr.open("POST", encodeURI("someURL"));

XMLHttpRequest.send()

The XMLHttpRequest.send() method is used to actually make an HTTP request. Its parameters are optional. If there are no parameters, it means that the HTTP request has only one URL and no data body. A typical example is a GET request; if it has parameters, it means that in addition to the header information, it also contains information containing specific data. Body, a typical example is a POST request.

The following is an example of a GET request.

var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.example.com/?id=" + encodeURIComponent(id), true);
xhr.send(null);

In the above code, the parameters of the GET request are appended to the URL as a query string.

The following is an example of sending a POST request.

var xhr = new XMLHttpRequest();
var data =
  "email=" +
  encodeURIComponent(email) +
  "&password=" +
  encodeURIComponent(password);

xhr.open("POST", "http://www.example.com", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(data);

Note that all the listening events of XMLHttpRequest must be set before the send() method is called.

The parameter of the send method is the data to be sent. Data in multiple formats can be used as its parameters.

void send();
void send(ArrayBufferView data);
void send(Blob data);
void send(Document data);
void send(String data);
void send(FormData data);

If send() sends a DOM object, the data will be serialized before sending. If sending binary data, it is best to send ArrayBufferView or Blob objects, which makes it possible to upload files via Ajax.

The following is an example of sending form data. The FormData object can be used to construct form data.

var formData = new FormData();

formData.append("username", "Zhang San");
formData.append("email", "zhangsan@example.com");
formData.append("birthDate", 1940);

var xhr = new XMLHttpRequest();
xhr.open("POST", "/register");
xhr.send(formData);

In the above code, the FormData object constructs the form data, and then uses the send() method to send it. Its effect is the same as sending the form data below.

<form id="registration" name="registration" action="/register">
  <input type="text" name="username" value="张三" />
  <input type="email" name="email" value="zhangsan@example.com" />
  <input type="number" name="birthDate" value="1940" />
  <input type="submit" onclick="return sendForm(this.form);" />
</form>

The following example uses the FormData object to process form data and then send it.

function sendForm(form) {
  var formData = new FormData(form);
  formData.append("csrf", "e69a18d7db1286040586e6da1950128c");

  var xhr = new XMLHttpRequest();
  xhr.open("POST", form.action, true);
  xhr.onload = function () {
    // ...
  };
  xhr.send(formData);

  return false;
}

var form = document.querySelector("#registration");
sendForm(form);

XMLHttpRequest.setRequestHeader()

The XMLHttpRequest.setRequestHeader() method is used to set the header information of the HTTP request sent by the browser. This method must be called after open() and before send(). If the method is called multiple times and the same field is set, the value of each call will be combined into a single value and sent.

The method accepts two parameters. The first parameter is a string, which represents the field name of the header information, and the second parameter is the field value.

xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Content-Length", JSON.stringify(data).length);
xhr.send(JSON.stringify(data));

The above code first sets the header information Content-Type, which means sending data in JSON format; then sets Content-Length, which means data length; and finally sends JSON data.

XMLHttpRequest.overrideMimeType()

The XMLHttpRequest.overrideMimeType() method is used to specify the MIME type, overriding the real MIME type returned by the server, so that the browser can handle it differently. For example, the data type returned by the server is text/xml. For various reasons, the browser fails to parse it and reports an error, and the data cannot be obtained at this time. In order to get the original data, we can change the MIME type to text/plain so that the browser will not automatically parse it, so we can get the original text.

xhr.overrideMimeType("text/plain");

Note that this method must be called before the send() method.

Modifying the data type returned by the server is not a method that should be taken under normal circumstances. If you want the server to return the specified data type, you can use the responseType property to tell the server, as in the following example. Only use the overrideMimeType() method when the server cannot return a certain data type.

var xhr = new XMLHttpRequest();
xhr.onload = function (e) {
  var arraybuffer = xhr.response;
  // ...
};
xhr.open("GET", url);
xhr.responseType = "arraybuffer";
xhr.send();

XMLHttpRequest.getResponseHeader()

The XMLHttpRequest.getResponseHeader() method returns the value of the specified field of the HTTP header. If the server has not received a response or the specified field does not exist, it returns null. The parameters of this method are not case sensitive.

function getHeaderTime() {
  console.log(this.getResponseHeader("Last-Modified"));
}

var xhr = new XMLHttpRequest();
xhr.open("HEAD", "yourpage.html");
xhr.onload = getHeaderTime;
xhr.send();

If there are multiple fields with the same name, their values ​​will be concatenated into a string, and each field will be separated by "comma+space".

XMLHttpRequest.getAllResponseHeaders ()

The XMLHttpRequest.getAllResponseHeaders() method returns a string representing all HTTP header information sent by the server. The format is a string, and each header information is separated by CRLF (carriage return + line feed). If no response from the server is received, this attribute is null. If a network error occurs, this attribute is an empty string.

var xhr = new XMLHttpRequest();
xhr.open("GET", "foo.txt", true);
xhr.send();

xhr.onreadystatechange = function () {
  if (this.readyState === 4) {
    var headers = xhr.getAllResponseHeaders();
  }
};

The above code is used to get all the header information returned by the server. It may be a string like the following.

date: Fri, 08 Dec 2017 21:04:30 GMT\r\n
content-encoding: gzip\r\n
x-content-type-options: nosniff\r\n
server: meinheld/0.6.1\r\n
x-frame-options: DENY\r\n
content-type: text/html; charset=utf-8\r\n
connection: keep-alive\r\n
strict-transport-security: max-age=63072000\r\n
vary: Cookie, Accept-Encoding\r\n
content-length: 6502\r\n
x-xss-protection: 1; mode=block\r\n

Then, process this string.

var arr = headers.trim().split(/[\r\n]+/);
var headerMap = {};

arr.forEach(function (line) {
  var parts = line.split(":");
  var header = parts.shift();
  var value = parts.join(": ");
  headerMap[header] = value;
});

headerMap["content-length"]; // "6502"

XMLHttpRequest.abort()

The XMLHttpRequest.abort() method is used to terminate the HTTP request that has been sent. After calling this method, the readyState property becomes 4, and the status property becomes 0.

var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.example.com/page.php", true);
setTimeout(function () {
  if (xhr) {
    xhr.abort();
    xhr = null;
  }
}, 5000);

The above code terminates an AJAX request 5 seconds after it is issued.

XMLHttpRequest instance events

readyStateChange event

When the value of the readyState property changes, the readyStateChange event will be triggered.

We can specify the listener function for this event through the onReadyStateChange property, and handle different states differently. Especially when the status changes to 4, it means that the communication is successful. At this time, the callback function can process the data sent back by the server.

progress event

When uploading files, both the XMLHttpRequest instance object itself and the upload property of the instance have a progress event, which will continuously return the upload progress.

var xhr = new XMLHttpRequest();

function updateProgress(oEvent) {
  if (oEvent.lengthComputable) {
    var percentComplete = oEvent.loaded / oEvent.total;
  } else {
    console.log("Unable to calculate progress");
  }
}

xhr.addEventListener("progress", updateProgress);

xhr.open();

load event, error event, abort event

The load event indicates that the data from the server has been received, the error event indicates that the request has an error, and the abort event indicates that the request is interrupted (for example, the user cancels the request).

var xhr = new XMLHttpRequest();

xhr.addEventListener("load", transferComplete);
xhr.addEventListener("error", transferFailed);
xhr.addEventListener("abort", transferCanceled);

xhr.open();

function transferComplete() {
  console.log("Data reception completed");
}

function transferFailed() {
  console.log("Data receiving error");
}

function transferCanceled() {
  console.log("User canceled receiving");
}

loadend event

The three events of abort, load and error will be accompanied by a loadend event, which indicates the end of the request, but it is not known whether it is successful.

xhr.addEventListener("loadend", loadEnd);

function loadEnd(e) {
  console.log("The request is over, the status is unknown");
}

timeout event

The server will trigger a timeout event if it does not return a result within the specified time. For specific examples, see the section on the timeout property.

When a user uninstalls a web page, it sometimes needs to send some data to the server. It is natural to use the XMLHttpRequest object to send data in the listener function of the unload event or the beforeunload event. However, this is not very reliable, because the XMLHttpRequest object is sent asynchronously, and it is likely that the page has been unloaded when it is about to be sent, causing the sending to be cancelled or the sending to fail.

The solution is to add some time-consuming synchronization operations to the unload event. This will allow enough time to ensure that asynchronous AJAX can be sent successfully.

function log() {
  let xhr = new XMLHttpRequest();
  xhr.open("post", "/log", true);
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  xhr.send("foo=bar");
}

window.addEventListener("unload", function (event) {
  log();

  // a time-consuming operation
  for (let i = 1; i < 10000; i++) {
    for (let m = 1; m < 10000; m++) {
      continue;
    }
  }
});

In the above code, a double loop is forced to execute, which prolongs the execution time of the unload event, causing the asynchronous AJAX to be sent successfully.

Similarly, you can use setTimeout. The following is an example of tracking user clicks.

// HTML code is as follows
// <a id="target" href="https://baidu.com">click</a>
const clickTime = 350;
const theLink = document.getElementById("target");

function log() {
  let xhr = new XMLHttpRequest();
  xhr.open("post", "/log", true);
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  xhr.send("foo=bar");
}

theLink.addEventListener("click", function (event) {
  event.preventDefault();
  log();

  setTimeout(function () {
    window.location.href = theLink.getAttribute("href");
  }, clickTime);
});

The above code uses setTimeout, which delays 350 milliseconds before allowing the page to jump, thus allowing time for asynchronous AJAX to be issued.

The common problem of these practices is that the unloading time is abruptly prolonged, the loading of subsequent pages is delayed, and the user experience is not good.

To solve this problem, the browser introduced the Navigator.sendBeacon() method. This method still sends the request asynchronously, but the request is disconnected from the current page thread, as the task of the browser process, so it can be guaranteed that the data will be sent without delaying the uninstall process.

window.addEventListener("unload", logData, false);

function logData() {
  navigator.sendBeacon("/log", analyticsData);
}

The Navigator.sendBeacon method accepts two parameters, the first parameter is the URL of the target server, and the second parameter is the data to be sent (optional), which can be of any type (string, form object, binary object, etc.) ).

navigator.sendBeacon(url, data);

The return value of this method is a boolean, and the data sent successfully is true, otherwise it is false.

The HTTP method for sending data in this method is POST, which can be cross-domain, similar to submitting data in a form. It cannot specify a callback function.

Below is an example.

// HTML code is as follows
// <body onload="analytics('start')" onunload="analytics('end')">

function analytics(state) {
  if (!navigator.sendBeacon) return;

  var URL = "http://example.com/analytics";
  var data = "state=" + state + "&location=" + window.location;
  navigator.sendBeacon(URL, data);
}