First, the front-end network request focus

In most cases, when making a network request at the front end, we only need to focus on the following points:

  • Pass in the base argument (url, request mode)
  • Request parameters, request parameter types
  • Set the request header
  • How to get the response
  • Gets the response header, response status, and response result
  • Exception handling
  • carrycookieSet up the
  • Cross-domain request

Two, the front-end network request way

  • formThe form,ifream, refresh the page
  • Ajax– The originator of asynchronous network requests
  • jQuery– An era
  • fetchAjaxThe replacement of
  • Axios, requestAnd many open source libraries

Three, about the network request question

  • AjaxWhat problem is solved by the emergence of
  • nativeAjaxHow to use
  • jQueryNetwork request mode
  • fetchThe usage and pit point
  • How to use it correctlyfetch
  • How to choose the appropriate cross-domain approach

With the above questions and concerns, we conduct a comprehensive analysis of several network requests.

What problems has Ajax solved

Before Ajax, web programs worked like this:

The drawbacks of this interaction are obvious. Any interaction with the server requires a page refresh, and the user experience is very poor. The advent of Ajax has solved this problem. Asynchronous JavaScript + XML

Using Ajax, web applications can quickly render incremental updates on the user interface without reloading (refreshing) the entire page.

Ajax itself is not a new technology, but rather describes a technical solution implemented using a collection of existing technologies, and the browser’s XMLHttpRequest is the most important object to implement Ajax (ActiveXObject is used in IE6 below).

Although X stands for XML in Ajax, JSON is now more commonly used than XML because of its many advantages, such as being lightweight and being part of Javascript.

The use of native Ajax

We’ll focus on the XMLHttpRequest object, and here are some basic uses:

        var xhr = new XMLHttpRequest();
        xhr.open('post'.'www.xxx.com'.true)
        // Receive the return value
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4) {if(xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {console.log(xhr.responseText); }}}// Process request parameters
        postData = {"name1":"value1"."name2":"value2"};
        postData = (function(value){
        var dataString = "";
        for(var key in value){
             dataString += key+"="+value[key]+"&";
        };
          return dataString;
        }(postData));
        // Set the request header
        xhr.setRequestHeader("Content-type"."application/x-www-form-urlencoded");
        // Exception handling
        xhr.onerror = function() {
           console.log('Network request failed')}// Carry cookies across domains
        xhr.withCredentials = true;
        // Make a request
        xhr.send(postData);
Copy the code

The following sections analyze the functions, attributes, and events commonly used by the XMLHttpRequest object.

function

open

Used to initiate a request. Usage:

xhr.open(method, url, async);
Copy the code
  • method: Request mode, such asThe get and post
  • url: the requesturl
  • async: Indicates whether the request is asynchronous

send

This method is used to send an HTTP request, that is, the HTTP request will not be sent until it is called.

xhr.send(param)
Copy the code
  • param: HTTP request parameter, which can beString, BlobSuch as type.

abort

This method is used to terminate an Ajax request. ReadyState is set to 0 after calling this method.

xhr.abort()
Copy the code

setRequestHeader

This method must be called between open() and send(). Usage:

xhr.setRequestHeader(header, value);
Copy the code

getResponseHeader

Gets the HTTP return header. If there are more than one of the same names in the return header, the value returned is a string separated by commas and Spaces. Usage:

var header = xhr.getResponseHeader(name);
Copy the code

attribute

readyState

Identifies the current state of the XMLHttpRequest object, which is always in one of the following states:

value state describe
0 UNSENT The proxy was created, but has not yet been invokedopen()Methods.
1 OPENED open()Method has been called.
2 HEADERS_RECEIVED send()The method has been called, and the header and state are available.
3 LOADING Download;responseTextProperty already contains some data.
4 DONE The download operation is complete.

status

Indicates the status of the HTTP request. The initial value is 0. If the server does not explicitly specify a status code, status is set to the default value of 200.

responseType

Indicates the data type of the response, and allows us to set it manually. If null, the default is text, which can have the following values:

value describe
"" willresponseTypeSet to empty string versus set to"text"Same, is the default type (actually isDOMString).
"arraybuffer" responseIt’s a binary dataJavaScript ArrayBuffer
"blob" responseIt’s a binary dataBlobObject.
"document" The response is aHTML DocumentorXML XMLDocumentDepending on the MIME type of the received data.
"json" responseIs a JavaScript object. This object is created by treating the received data type asJSONAnalytic.
"text" responseIs included in theDOMStringObject.

response

Returns the body of the response, of the type determined by the responseType above.

withCredentials

Ajax requests by default carry cookies for same-origin requests, while cross-domain requests do not. Setting the withCredentials attribute of XHR to true allows cross-domain cookies.

Event callback

onreadystatechange

 xhr.onreadystatechange = callback;
Copy the code

Callback is triggered when the readyState property changes.

onloadstart

 xhr.onloadstart = callback;
Copy the code

Before the Ajax request is sent (after readyState==1, before readyState==2), the callback will be triggered.

onprogress

xhr.onprogress = function(event){
  console.log(event.loaded / event.total);
}
Copy the code

The function returns the total size of the loaded resource as loaded, which is used to calculate the loading progress.

onload

 xhr.onload = callback;
Copy the code

Callback is triggered when a resource and its dependent resources have finished loading, and normally we process the return value in the onLoad event.

Exception handling

onerror

 xhr.onerror = callback;
Copy the code

The callback is triggered when the Ajax resource fails to load.

ontimeout

 xhr.ontimeout = callback;
Copy the code

Callback is triggered when progress terminates due to a predetermined time expiration, which can be set using the timeout attribute.

JQuery encapsulates Ajax

For a long time, people used jQuery’s Ajax wrapper for web requests, including $. Ajax, $. Get, $. Post, etc., which I still find useful today.

$.ajax({
    dataType: 'json'.// Set the return value type
    contentType: 'application/json'.// Set the parameter type
    headers: {'Content-Type'.'application/json'},// Set the request header
    xhrFields: { withCredentials: true }, // Carry cookies across domains
    data: JSON.stringify({a: [{b:1.a:1}}),// Pass parameters
    error:function(xhr,status){  // Error handling
       console.log(xhr,status);
    },
    success: function (data,status) {  // Get the result
       console.log(data,status); }})Copy the code

$. Ajax takes a single parameter, which takes a set of configurations and wraps itself into a jqXHR object. Read the Jquary-Ajax source code

Common configurations:

url

Current page address. The address from which the request is sent.

type

Type: String Request type (“POST” or “GET”). The default is “GET”. Note: Other HTTP request methods, such as PUT and DELETE, are also available, but only supported by some browsers.

timeout

Type: Number Specifies the request timeout period (ms). This setting overrides global Settings.

success

Type: Function Callback Function after a successful request.

jsonp

Override the name of the callback function in a JSONP request. This value is used instead of the “callback=? The “callback” part of the URL parameter in this GET or POST request.

Error type: Function. This function is called when the request fails.

Note: Error determination in source code:

isSuccess = status >= 200 && status < 300 || status === 304;
Copy the code

Return values other than these status codes will carry an error callback.

dataType

"xml": Returns an XML document, which can be processed using jQuery."html": Returns plain text HTML information; The included script tag is executed when the DOM is inserted."script": Returns plain text JavaScript code. Results are not cached automatically. Unless it's set"cache"Parameters. Note: In the case of remote requests (not in the same domain), all POST requests are converted to GET requests. (Because it will be loaded using the DOM's script tag)"json"Returns theJSONThe data."jsonp": JSONP format. When a function is called using the JSONP form, for example"myurl? callback=?"JQuery will automatically replace? Is the correct function name to execute the callback function."text": Returns a plain text stringCopy the code

data

Type: String Uses json. stringify transcoding

complete

Type: Function Callback Function after request completion (called after request success or failure).

async

Type: Boolean Default value :true. By default, all requests are asynchronous. If you need to send synchronization requests, set this option to false.

contentType

Type: String Default: “Application/X-www-form-urlencoded”. Type of content encoding when sending information to the server.

This organization of key-value pairs is fine in general. This is generally done without nested JSON, i.e. simple JSON, which looks something like this:

{
    a: 1.b: 2.c: 3
}
Copy the code

But in some complicated cases there is a problem. For example in the Ajax you to preach a complex json to like, also as object embedded arrays, including object in the array, you pass like this: application/x – WWW – form – urlencoded this form is that there is no way to organize the key value from complex json form.

{
  data: {
    a: [{
      x: 2}}}]Copy the code

You can pass complex JSON objects as follows

$.ajax({
    dataType: 'json'.contentType: 'application/json'.data: JSON.stringify({a: [{b:1.a:1}]})})Copy the code

Alternatives to jQuery

With the development of front-end MV* in recent years, people are using jQuery less and less. It is impossible for us to introduce it just to use jQuery’s Ajax API. Inevitably, we need to find a new technical solution.

In his document, Yu Creek recommends using Axios for web requests. Axios wraps the native XHR around Promise in a very elegant way. Axios also provides node support, making it the preferred solution for network requests.

In the future, there will certainly be more excellent encapsulation, which has very comprehensive consideration and detailed documentation. Here, we will not do more research, but focus on the underlying APIfetch.

The Fetch API is a powerful native API for accessing and manipulating HTTP pipes.

This functionality was previously implemented using XMLHttpRequest. Fetch provides a better alternative that can easily be used by other technologies, such as Service Workers. Fetch also provides a single logical location to define other HTTP-related concepts, such as CORS and HTTP extensions.

You can see that FETCH appears as an alternative to XMLHttpRequest.

With FETCH, you don’t need to load an additional external resource. But it’s not fully supported by browsers yet, so you’ll still need a Polyfill.

The use of FETCH

A basic fetch request:

const options = {
    method: "POST".// Request parameters
    headers: { "Content-Type": "application/json"}, // Set the request header
    body: JSON.stringify({name:'123'}), // Request parameters
    credentials: "same-origin"./ / cookie Settings
    mode: "cors"./ / across domains
}
fetch('http://www.xxx.com',options)
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(myJson); // Response data
  })
  .catch(function(err){
    console.log(err); // Exception handling
  })
Copy the code

The Fetch API provides a global Fetch () method and several helper objects to initiate a network request.

  • fetch()

The fetch() method is used to initiate a request for a resource. It returns a promise that will be resolved after requesting a Response and returns a Response object.

  • Headers

You can create your own Headers object using the Headers() constructor, which is equivalent to the response/ Request Headers and allows you to query these Headers or do different things for different results.

var myHeaders = new Headers();
myHeaders.append("Content-Type"."text/plain");
Copy the code
  • Request

The Request() constructor creates a Request object that can be used as the second argument to the fetch function.

  • Response

Fetch () returns a Response instance after Promises are processed. You can also create a Response instance manually.

Fetch Polyfill source code analysis

Fetch is a very low-level API, so we cannot further explore its underlying level, but we can explore its basic principles and find out the pit points with the help of its polyfill.

The code structure

As you can see from the code, Polyfill encapsulates the four main objects provided by the Fetch API:

The fetch encapsulation

The code is very clear:

  • To construct aPromiseObject and return
  • To create aRequestobject
  • To create aXMLHttpRequestobject
  • Take out theRequestRequests in objectsurl, sent by the requesting party,openaxhrRequest and willRequestObject stored inheadersTake out and assign to XHR
  • xhr onloadAfter take outresponsethestatus,headers,bodyencapsulationResponseObject, callresolve.

Exception handling

As you can see, there are three possibilities for calling Reject:

  • 1. The request timed out

  • 2. The request fails

Note: OnError cannot be raised when you establish a profile with the server and receive an exception status code from the server such as 404, 500, etc. The onError flag is reject only when the network is faulty or the request is blocked. For example, if the network is trans-domain, the URL does not exist, or the network is abnormal, onError is triggered.

Therefore, fetch will enter THEN instead of Catch when receiving an abnormal status code. These error requests are often handled manually.

  • 3. Manually terminate the service

You can pass the Signal object to the request argument and add the ABORT event listener to the signal object. When xhr.readyState changes to 4 (the response has been parsed), the abort event listener is removed.

This means that a FETCH request can be terminated by calling signal.abort before it ends. You can create a controller in a browser using the AbortController() constructor and then use the abortController.signal property

This is an experimental feature that is still under development in some browsers

Headers to encapsulate

A map object is maintained in a header object. The constructor can pass in a header object, an array, a header of a normal object type, and maintain all the values in the map.

ForEach: fetch forEach: forEach: forEach: forEach: forEach: forEach: forEach: forEach: forEach

See that the traversal of the header is the traversal of its internal map.

The Header also provides methods such as Append, DELETE, GET, and set, which operate on the internal map object.

The Request object

The two parameters received by the Request object are the two parameters received by the FETCH function. The first parameter can be passed either directly to the URL or to a constructed Request object. The second parameter is the Option object that controls the different configurations.

You can pass in attributes such as credentials, headers, method, mode, signal, and referrer.

Note here:

  • The incomingheadersBe used as aHeadersConstructor to construct the Header object.

Cookie handling

Fetch also has the following code:

    if (request.credentials === 'include') {
      xhr.withCredentials = true
    } else if (request.credentials === 'omit') {
      xhr.withCredentials = false
    }
Copy the code

The default credentials type is same-Origin, and they can carry the Coodkie of the same origin request.

Then I find that the implementation of polyfill here is inconsistent with MDN- using Fetch and a lot of data:

MDN: By default, FETCH does not send or receive any cookies from the server

Therefore, I tested the cases of using polyfill and native FETCH to carry cookies respectively, and found that both of them carry same-origin cookies by default when the credentials are not set, which is inconsistent with the description in the document. Fetch does not carry cookies by default. Here is how to use a native FETCH in a browser:

Then I noticed in MDN-fetch -Request that the default value of the new browser credentials has been changed to SAME-origin and the previous version is still omit.

It is true that MDN- using Fetch here the document is not updated in time, which is misleading…

The Response object

The Response object is the return value of a successful fetch call:

Review the operation on Response in fetch:

    xhr.onload = function () {
      var options = {
        status: xhr.status,
        statusText: xhr.statusText,
        headers: parseHeaders(xhr.getAllResponseHeaders() || ' ')
      }
      options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
      var body = 'response' in xhr ? xhr.response : xhr.responseText
      resolve(new Response(body, options))
    }
Copy the code

Response constructor:

It can be seen that in the constructor, status, statusText, headers and URL of options are processed and mounted to the Response object respectively.

The responseText constructor does not explicitly handle the responseText function. The responseText constructor does not explicitly declare the _initBody property, and the responseText constructor calls the Body function. The _initBody function is mounted to Response via the Body function.

As you can see, the _initBody function depends on the type of xhr.response (Blob, FormData, String…). Assign values to different parameters that have different uses in the Body method. Here’s what else the Body function does:

The Body function also mounts four functions for the Response object, text, json, blob, and formData. The operations in these functions are to return different types of values from _initBody.

This also indicates that after the fetch is completed, the return value cannot be directly obtained in response, but must be obtained by calling functions such as text() and json().

One more point: several functions have logic like the following:

    var rejected = consumed(this)
    if (rejected) {
      return rejected
    }
Copy the code

Consumed function:

function consumed(body) {
  if (body.bodyUsed) {
    return Promise.reject(new TypeError('Already read'))
  }
  body.bodyUsed = true
}
Copy the code

The bodyUsed variable is set to true after each call to text(), json(), etc., indicating that the return value has been read. The next call raises TypeError(‘Already read’). This also follows the principles of native FETCH:

Since the Responses objects were set up as stream, they could only be read once

X. The pit point of fetch

Fetch is described in the VUE documentation as follows:

There are a number of other considerations when using FETCH, which is why people still prefer Axios more at this point. Of course, that could change in the future.

Since FETCH is a very low-level API, it is not wrapped very much, and there are many issues to deal with:

  • Can’t pass directlyJavaScriptObject as a parameter
  • You need to determine the type of return value and execute the method that gets the return value in response
  • The return value method can only be called once, not more than once
  • Failed to catch an exception properly
  • Older browsers don’t carry it by defaultcookie
  • Does not supportjsonp

11. Fetch encapsulation

Request parameter handling

Different parameter types can be passed in:

function stringify(url, data) {
  var dataString = url.indexOf('? ') = =- 1 ? '? ' : '&';
  for (var key in data) {
    dataString += key + '=' + data[key] + '&';
  };
  return dataString;
}

if (request.formData) {
  request.body = request.data;
} else if (/^get$/i.test(request.method)) {
  request.url = `${request.url}${stringify(request.url, request.data)}`;
} else if (request.form) {
  request.headers.set('Content-Type'.'application/x-www-form-urlencoded; charset=UTF-8');
  request.body = stringify(request.data);
} else {
  request.headers.set('Content-Type'.'application/json; charset=UTF-8');
  request.body = JSON.stringify(request.data);
}
Copy the code

Cookies carry

Fetch has started to carry same-origin cookie by default in the new version of the browser, but it will not carry same-origin cookie by default in the old version of the browser. We need to set it uniformly:

  request.credentials =  'same-origin'; // Homologous carry
  request.credentials =  'include'; // Can be carried across domains
Copy the code

Exception handling

When an HTTP status code representing an error is received, the Promise returned from fetch() is not marked as reject, even if the HTTP response’s status code is 404 or 500. Instead, it marks the Promise state as resolve (but sets the OK attribute of the return value of Resolve to false), and only as Reject when the network fails or the request is blocked.

Therefore, we need to handle fetch exceptions uniformly

.then(response= > {
  if (response.ok) {
    return Promise.resolve(response);
  }else{
    const error = new Error('Request failed! Status code:${response.status}, failure message:${response.statusText}`);
    error.response = response;
    return Promise.reject(error); }});Copy the code

Return value processing

Call the receiver function for different types of return values. The type must be determined in order to avoid multiple calls to the return value method:

.then(response= > {
  let contentType = response.headers.get('content-type');
  if (contentType.includes('application/json')) {
    return response.json();
  } else {
    returnresponse.text(); }});Copy the code

jsonp

Fetch itself does not provide support for JSONP, and JSONP itself is not a very good way to solve cross-domain. It is recommended to use CORS or NginX to solve cross-domain. Please refer to the following sections for details.

Fetch is wrapped and ready to be used happily.

HMM, Axios works so well…

Xii. Cross-domain summary

When it comes to network requests, cross domains have to be mentioned.

The browser’s same-origin policy restricts how documents or scripts loaded from the same source can interact with resources from another source. This is an important security mechanism for isolating potentially malicious files. Read operations between different sources are generally not allowed.

Cross – domain conditions: protocol, domain name, port, if there is a difference is cross – domain.

Here are a few ways to work across domains:

nginx

Cross-domain implementation using nginx reverse proxies, see my article: NGINx Essentials for Front-end Developers

cors

CORS is a W3C standard, which stands for “Cross-origin Resource Sharing”. It allows the browser to issue XMLHttpRequest requests across source servers.

To enable CORS, set access-Control-allow-Origin on the server. This attribute indicates which domain names can access resources. If a wildcard is set, all websites can access resources.

app.all(The '*'.function (req, res, next) {
    res.header("Access-Control-Allow-Origin"."*");
    res.header("Access-Control-Allow-Headers"."X-Requested-With");
    res.header("Access-Control-Allow-Methods"."PUT,POST,GET,DELETE,OPTIONS");
    next();
});
Copy the code

jsonp

The link in the SRC attribute of the script tag can access cross-domain JS scripts. With this feature, the server no longer returns JSON data, but returns a piece of JS code that calls a function, which is called in SRC, so that cross-domain is achieved.

Jquery support for JSONP

        $.ajax({
            type : "get".url : "http://xxxx"
            dataType: "jsonp".jsonp:"callback".jsonpCallback: "doo".success : function(data) {
                console.log(data); }});Copy the code

Fetch, Axios, etc., do not provide direct support for JSONP. If this is needed, we can try manual encapsulation:

(function (window,document) {
    "use strict";
    var jsonp = function (url,data,callback) {

        // 1. Convert the incoming data into a URL string
        // {id:1,name:'jack'} => id=1&name=jack
        var dataString = url.indexof('? ') = =- 1? '? ': '&';
        for(var key in data){
            dataString += key + '=' + data[key] + '&';
        };

        // 2 Handles callbacks in urls
        // cbFuncName Specifies the name of the callback function: my_json_cb_ prefix + random number
        var cbFuncName = 'my_json_cb_' + Math.random().toString().replace('. '.' ');
        dataString += 'callback=' + cbFuncName;

        // 3. Create a script tag and insert it into the page
        var scriptEle = document.createElement('script');
        scriptEle.src = url + dataString;

        // 4. Mount callback function
        window[cbFuncName] = function (data) {
            callback(data);
            // After processing the callback data, remove the jSONp script tag
            document.body.removeChild(scriptEle);
        }

        document.body.appendChild(scriptEle);
    }

    window.$jsonp = jsonp; }) (window.document)
Copy the code

PostMessage cross-domain

The postMessage() method allows scripts from different sources to communicate asynchronously in a limited manner, enabling cross-text file, multi-window, cross-domain messaging.

/ / capture the iframe
var domain = 'http://scriptandstyle.com';
var iframe = document.getElementById('myIFrame').contentWindow;

// Send a message
setInterval(function(){
	var message = 'Hello! The time is: ' + (new Date().getTime());
	console.log('blog.local: sending message: ' + message);
        //send the message and target URI
	iframe.postMessage(message,domain); 
},6000);
Copy the code
// Respond to events
window.addEventListener('message'.function(event) {
	if(event.origin ! = ='http://davidwalsh.name') return;
	console.log('message received: ' + event.data,event);
	event.source.postMessage('holla back youngin! ',event.origin);
},false);
Copy the code

PostMessage is applicable to the following scenarios: Cross-domain communication between multiple Windows of a browser and cross-domain communication between IFrames.

WebSocket

WebSocket is a bidirectional communication protocol. After a connection is established, both the WebSocket server and client can actively send or receive data to each other without being restricted by the same Origin policy.

         function WebSocketTest(){
            if ("WebSocket" in window){
               alert("Your browser supports WebSocket!");
               // Open a Web socket
               var ws = new WebSocket("ws://localhost:3000/abcd");
               ws.onopen = function(){
                  // The Web Socket is connected, use the send() method to send data
                  ws.send("Send data");
                  alert("Data in transit...");
               };
               ws.onmessage = function (evt) { 
                  var received_msg = evt.data;
                  alert("Data received...");
               };
               ws.onclose = function(){ 
                  / / close the websocket
                  alert("Connection closed..."); 
               };
            } else{
               // Browsers do not support WebSocket
               alert("Your browser does not support WebSocket!"); }}Copy the code

The article first

To read more excellent articles, or to read the mind mapping source files for articles, follow my Github blog at star✨.

Fundebug is a very useful BUG monitoring tool

If there are any mistakes in this article, please correct them in the comments section. Thank you for reading.

After concern public number reply [add group] pull you into high quality front end communication group.