Ajax is an acronym, taken from the acronyms Asynchronous JavaScript And XML.

The difference between synchronous and asynchronous requests

The first step in learning Ajax is to understand the difference between synchronous and asynchronous requests.

Synchronous request: FORM

In the early days of the Web, the main way for clients and servers to interact was through the form tag. The form tag is easy for users to use, and needs to set the action and method attributes. Forms contain interactive elements with input functions, such as input, checkbox, or SELECT. These interactive elements set a name as the name of the field. Finally, a Submit component submits the data to the interface set by the action. For example, if you want to submit a login form, the code looks something like this:

<form action="http://example.com/" method="post">
  <input name="accessName" placeholder="Please enter your user name." />
  <input name="password" type="password" placeholder="Please enter the user password" />
  <button type="submit">landing</button>
</form>
Copy the code

The Form tag has a number of features that I’ve categorized into advantages and disadvantages as follows.

The advantages of the form

Forms belong to an early DOM interface that is independent of JavaScript and can send HTTP requests even if the browser does not have JavaScript enabled. So it’s very compatible and still works in some older browsers. In my opinion, this is the only advantage of form.

The form of the defect

Aside from this one advantage, the other features of the Form tag are almost all disadvantages for modern browsers. There are two main disadvantages:

  • Only GET and POST request methods are supported.
  • The form changes the URL and refreshes the entire HTML document.
  • You must wait for the server program to complete before loading a new HTML document.

The first drawback is the lack of functionality. The current HTTP interface can be set in many ways, not only get and POST, but also patch, PUT, DELETE, and Options. The latter two disadvantages are fatal and directly affect the user experience. If you call the interface every time, you have to refresh the entire HTML document, which greatly reduces the user experience. A project with a poor user experience is unlikely to succeed. Especially with today’s increasingly picky users.

TODOList Demo

The overall architecture of form-based projects for data interaction is usually MVC pattern. The front and back ends are usually not separated, but data is filled through templates. Many front-end engineers with fewer years of work experience this pattern, but I think it’s a must-know to learn about Ajax. After all, any new technology in the computer is almost born to solve some disadvantages of the old technology. If you learn new technology directly without looking at its historical background, it is often difficult to understand the original intention of its birth. Here is an extremely simple example of TODOList implemented with Express. Express is a server framework implemented in Node.js. The purpose of this Demo is to help us understand the real situation of synchronous requests, without paying attention to the details. Here’s a complete process to take into account the level of the reader: Create a project.

mkdir todo-list
Copy the code

Enter the project.

cd todo-list
Copy the code

Initialize the NPM project.

npm init -y
Copy the code

Install the Express and EJS template engines.

npm i express ejs
Copy the code

Write server-side programs. It only did two things:

  1. Store data.
  2. Render the template and return HTML.
var express = require("express");
var ejs = require("ejs");
var app = express();

const todoList = []

app.get("/".function (req, res) {
  // Store data
  if(req.query.todo){
    todoList.push(req.query.todo)
  }
  // Render the template and return HTML
  ejs.renderFile("index.html",
    {todoList},
    (err, data) = > {
      res.end(data)
    })
})

app.listen(3000);
Copy the code

Write template page, <% %> is the SYNTAX of EJS, you do not need to learn EJS, just need to understand the general role of it.

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>TODO List</title>
  </head>
  <body>
    <form action="http://localhost:3000/" method="get">
      <input name="todo" />
      <button type="submit">add</button>
    </form>
    <div>
      <% for (var i=0; i<todoList.length; i++) { %>
        <li> 
          <%= todoList[i] %>    
        </li>The < %} % ></div>
  </body>
</html>
Copy the code

Finally, start the service.

node server.js
Copy the code

accesshttp://localhost:3000/”And then you can experience TODO.

From the example above, we can see that the HTML structure under the back-end MVC architecture is roughly as shown in the figure below.



Let’s change the code again to experience a bad user experience. Modify the render template part of the code, assuming a 2 second operation.

ejs.renderFile("./index.html", { todoList }, (err, data) = > {
  setTimeout(() = > res.end(data), 2000);
});
Copy the code

Go back to the TODO List and you’ll see that every time you add a to-do item, the page takes more than 2 seconds to load. For more than two seconds, we couldn’t do anything but wait for the browser to load.

Ajax and XMLHttpRequest

To address the user experience problems associated with forms, the father of Ajax, Jesse James Garrett, published Ajax: A New Approach to Web Applications introduces the concept of Ajax. This point in time is a milestone for the Web. Before that, it was Web 1.0, and after that, it was Web 2.0. Ajax means making interface requests using JavaScript and XML in an asynchronous manner that does not require a page refresh and supports all request methods. Therefore, the shortcomings of form are perfectly solved. The first representative technique is XMLHttpRequest, abbreviated XHR. XHR predates Ajax, being built into Microsoft’s Internet Explorer 5 browser in 1999. But it was a handful of great Web apps, notably Google’s famous Gmail, that really caught on. Gmail was one of the best Web apps ever, and it’s still one of the best Web apps ever, even today. It was not until 2006 that the W3C standardized it, and since then all browsers have had the API. Now that you’ve gotten your first taste of Ajax and XHR, let’s take a quick look at XHR. Its basic usage is as follows:

const xhr = new XMLHttpRequest();
xhr.open('GET'.'http://example.com/'.true);
xhr.setRequestHeader("Content-type"."application/x-www-form-urlencoded");  
xhr.responseType = 'json';

xhr.addEventListener("load".function () {
  // TODO:The logic of success
});

xhr.addEventListener("error".function () {
  // TODO:Failed logic
});

xhr.send();
Copy the code

We first create an XHR object using new XMLHttpRequest. Set the basic request configuration using the open method. The first parameter is the request method, the second parameter is the request URL, and the third parameter describes whether the request is asynchronous or synchronous. True means asynchronous, false means synchronous, and default is asynchronous. The setRequestHeader method sets the request header fields, content-type representing the format of the Content. ResponseType specifies the format of the data to be returned. The default is TEXT. Here we’re going to set it to JSON. An XHR object can listen to a number of methods via addEventListener, the common ones being the Load method that fires when the call ends and the Error method that fires when the call fails. The final call is made through the send method, which can take an optional argument as the body of the request.

Refactor TODOList using XHR

The overall architecture of a project that uses Ajax to separate the front and back ends usually looks like this:



We can use XHR to modify a previously written TODOList application.

First, you need to install the body-Parser library, which helps Express format the body.

npm i body-parser
Copy the code

Refactoring server. Js.

var express = require("express");
var path = require("path");
var app = express();
var bodyParser = require('body-parser');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }))

const todoList = []

app.get("/".function (req, res) {
  res.sendFile(path.resolve(__dirname, './index.html'))
})

app.get("/todo".function (req, res) {
  res.send(todoList)
})

app.post("/todo".function (req, res) {
  if (req.body.todo) todoList.push(req.body.todo)
  res.send(JSON.stringify({ status: 'success' }))
})

app.listen(3000);
Copy the code

Two interfaces have been added, todo for the GET method and TOdo for the POST method, to get data and to save data, respectively. Refactoring index. HTML.

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>TODO List</title>
  </head>
  <body>
    <input id="todoInput" />
    <button id="addBtn">add</button>
    <div id="list"></div>
    <script>
      document.getElementById("addBtn").addEventListener("click", add);
      const todoInput = document.getElementById("todoInput");

      function add() {
        const xhr = new XMLHttpRequest();
        xhr.open("post"."http://localhost:3000/todo");
        xhr.responseType = "json";
        xhr.setRequestHeader("Content-Type"."application/json");
        xhr.addEventListener("load".function () {
          if (xhr.response.status === "success") {
            todoInput.value = ""; getData(); }});const data = JSON.stringify({ todo: todoInput.value });
        xhr.send(data);
      }

      function getData() {
        const xhr = new XMLHttpRequest();
        xhr.open("get"."http://localhost:3000/todo");
        xhr.responseType = "json";
        xhr.addEventListener("load".() = > {
          renderList(xhr.response);
        });
        xhr.send();
      }

      function renderList(todoList) {
        document.getElementById("list").innerHTML = todoList
          .map((i) = > `<li>${i}</li>`)
          .join("");
      }

      getData();
    </script>
  </body>
</html>
Copy the code

We added a JavaScript script code to the HTML. The main function is to create an XHR object after clicking the Add button, and send the asynchronous request through the object. The content-type of the request header field is set to Application /json. The content format of the data to be sent is JSON. More on the difference between JSON and XML later. After the request is returned, if the status is SUCCESS, the request is successful, and then the getData function is called to get the latest data, and the data is rendered to the list element, so that the page does not jump, only in the current page partial refresh effect. At this point, we are finished refactoring TODOList with Ajax.

Common properties of XHR objects

readyState

ReadyState is a property of type Number with five values ranging from 0 to 4 that represent the current state of the XHR object.

  • 0: not sent, the XHR object has been created, but the open method has not been called.
  • 1: the open method is called, but the send method is not called.
  • 2: The response is sent, the send method is called, and the header and state are accessible, but the response body is not yet accessible.
  • 3: Download, start to receive returned data, data is not complete at this time.
  • 4: Complete, accept all response data.

We can also think of this process as the life cycle of a request.

ReadyState constants

There are also five constants in the XHR object, corresponding to the five states of the readyState property, to provide better semantics and ease of memorization. This allows us to directly compare XHR states using these constants.

  • UNSENT-0
  • OPENED-1
  • HEADERS_RECEIVED-2
  • LOADING-3
  • DONE-4

The status and statusText

Status represents the HTTP status code. It consists of three decimal arrays, with the first digit representing the type of the status code and the next two digits representing the more granular meaning. HTTP status codes fall into five large types. The values start with 1 to 5, and 200 indicates that the request is successful. 404 indicates that the resource is not found. 500 indicates an internal server error. Other more specific categories will be introduced later in this article. StatusText is a semantic description of status. For example, if the status is 200, the corresponding statusText should be OK. For 404, statusText should be Not Found. Both status and statusText are set by the server.

response*

There are five properties starting with response: Response, responseType, responseURL, responseText, and responseXML.

  • Response is a read-only property that represents the data returned, and its value depends on the value of responseType.
  • ResponseType is the data type we set. We can set the string enumeration value (arrayBuffer, blob, Document, JSON). If nothing is set, the default is text. This property must be compatible with the data format returned by the server, otherwise the data format will be confused.
  • ResponseURL is the serialized responseURL.
  • ResponseText, the read-only property, returns the plain text value of the data.
  • ResponseXML, read-only property, returns a Document object. Note that using responseText and responseXML will raise an exception if the responseType value is not set to document or an empty string.

timeout

If the server keeps not returning a response, the XHR object keeps consuming resources. For example, if the network connection is unstable, there needs to be a mechanism to close the request. The timeout attribute is designed for this requirement.

The value of timeout can be set to Number in milliseconds. When the response time exceeds this value, the request is closed.

upload

Ideally, interface returns should be very fast, both at the millisecond level. However, in some special scenarios, interface requests may be slow, such as uploading large files. At this time, we need to know the current upload progress, so as to provide real-time prompts to users and reduce their anxiety. The Upload attribute is designed for this requirement. Upload returns an XMLHttpRequestUpload object. Listening for the onProgress event on this object will get the current upload progress.

XHR object methods

XHR’s methods are relatively simple and simple in functionality, so I’ll skip over them here.

open

Initializes a request with Method as the first parameter and URL as the second parameter.

send

Start sending the request. You can pass one parameter as the Body of the Post request. If XHR is synchronous, it will block.

setRequestHeader

Sets the fields of the request header with the first parameter being the field name and the second parameter being the field value.

getResponseHeader

Gets a response header field.

getAllResponseHeaders

Gets all the response header fields.

abort

Interrupt the request immediately.

overrideMimeType

Overrides the MIME type returned by the server.

XHR object events

XHR has 8 events, which are abort, Error, Load, loadend, loadstart, progress, readyStatechange, and Timeout. It’s also easy to read literally, which corresponds to a request break, a load exception, a load success, a load end, a load start, a load in progress, a readState change, and a request timeout. The listening method is also simple, using the on* = callback method. In addition to the on* approach, you can also listen for these events using addEventListener. I prefer to use the addEventListener approach to listen for events.

XML and JSON

XML 和 JSONBoth are data transmission formats.

Although both Ajax and XMLHttpRequest have an XML in their name, the most popular data transfer format today is no longer XML, but JSON.

Before 2008, the most popular data transmission format was XML. After 2008, JSON began to appear in people’s vision, gradually accepted by people, and quickly popularized, and began to become the strongest competitor of XML. Until 2013, JSON completely overtook XML to become the most popular data transfer format. Projects that still use XML as a data transfer format are often projects with very deep historical roots. So if you haven’t heard of XML, or have only heard of it, it’s normal not to use IT.

Below is a chart showing the trend of different data transmission formats over the decade 2009-2019. In addition to XML and JSON, there are other data transfer formats such as CSV and SOAP.

Since JSON is the most popular data transfer format, why aren’t Ajax and XMLHttpRequest called Ajaj and JSONHttpRequest?

The answer to these questions is historical. XML was born in 1997, XMLHttpRequest in 1999, and JSON in 2001. So when XMLHttpRequest came along, JSON didn’t exist, it certainly couldn’t be called JSONHttpRequest, and XMLHttpRequest was originally just fetching XML data.

Once a technology is standardized and popularized, compatibility issues must be considered, and changing the name is impractical. It would be redundant to create another JSONHttpRequest object, since XMLHttpRequest can do the same thing with JSON data.

The concept of Ajax was born in 2005, but JSON went mainstream 12 years later, so Ajax wouldn’t be called Ajaj, nor did it need to be renamed, and people have gotten used to it over the years.

So what’s the difference between XML and JSON in terms of actual data formats? Take the data from the TODOList interface as an example.

XML might have the following structure:

<list>
  <item>Reading a book</item>
  <item>Disco dancing</item>
  <item>writing</item>
  <item>Brush on zhihu</item>
</list>
Copy the code

The JSON format might look something like this:

["Reading"."Disco dancing"."Writing"."Brush zhihu"]
Copy the code

I won’t go into detail here about the grammatical and other differences between the two. If you’re not familiar with JSON, you can follow the link at the beginning of this section. However, I don’t recommend that you study the XML syntax in detail, because it is outdated and doesn’t make much sense. It might be a better choice to prioritize learning JSON.

Reconstruct the TODO List using XML

To get an intuitive sense of the difference between XML and JSON, let’s reconstruct the TODO List using XML. Modify the Todo interface of server.js.

app.get("/todo".function (req, res) {
  if (req.query.format === "xml") {
    return res.send(
      `<list>${todoList.map((item) => `<item>${item}</item>`).join("")}</list>`
    );
  }
  res.send(todoList);
});
Copy the code

The format field is added to the query parameters of REQ to be compatible with BOTH XML and JSON formats. The json format is still supported by default. If the format field is passed and set to XML, the data in XML format is returned. The returned data is now in XML format. The above programming pattern is useful in many scenarios. Next, modify the getData function in index.html.

function getData() {
  const xhr = new XMLHttpRequest();
  xhr.open("get"."http://localhost:3000/todo? format=xml");
  xhr.addEventListener("load".() = > {
    const parser = new DOMParser();
    const xml = parser.parseFromString(xhr.response, "text/xml");
    const todoList = Array.from(xml.getElementsByTagName("item")).map(
      (itemEl) = > itemEl.innerHTML
    );
    renderList(todoList);
  });
  xhr.send();
}
Copy the code

We first add the format= XML parameter to the URL and unset the xhr.responseType so that the default text is used. The returned data is then parsed into XML using a DOMParser object. Then use the DOM API to query all item tags and convert them into JavaScript objects. By comparison, we can draw the following conclusions. JSON can replace XML for two main reasons:

  • JSON can be easily converted into JavaScript native objects, which are easier to parse and access.
  • JSON has a smaller data transfer volume.

Practical tips for real world situations

Inform users of the current upload and download progress to reduce waiting anxiety.

Let’s say I’m downloading a large image with a bad Internet connection. There are usually two ways to download an image. The first way is to set the SRC attribute directly on the img tag, and the browser automatically downloads and displays the image. Because it loads as a stream of data, the visual effect is that the image loads line by line from top to bottom of the page, moving from blurry to clear. The other option is to have JavaScript code perform the download and then display the image once it has been fully downloaded. In this scenario, the image will not be loaded as described above. To demonstrate the effect, let’s use the second method. Write server-side code:

var express = require("express");
var ejs = require("ejs");
var path = require("path");
var app = express();
var bodyParser = require("body-parser");

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

const todoList = [];

app.get("/".function (req, res) {
  res.sendFile(path.resolve(__dirname, "./index.html"));
});

app.get("/img".function (req, res) {
  res.sendFile(path.resolve(__dirname, "./img.jpg"));
});

app.listen(3000);
Copy the code

Write index. HTML.

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>img</title>
  </head>
  <body>
    <div id="container"></div>
    <div id="procress"></div>
    <script>
      const procress = document.getElementById("procress");
      const container = document.getElementById("container");

      const xhr = new XMLHttpRequest();
      xhr.open("GET"."http://localhost:3000/img");
      xhr.responseType = "blob";
      xhr.addEventListener("progress".(e) = > {
        procress.innerText = 'Loaded:The ${Math.floor(
          (e.loaded / e.total) * 100
        )}`;
      });
      xhr.addEventListener("load".(e) = > {
        const img = new Image();
        img.style.width = "100%";
        img.src = URL.createObjectURL(e.currentTarget.response);
        container.replaceWith(img);
      });
      xhr.send();
    </script>
  </body>
</html>
Copy the code

Place the resource picture.

You can use this image of mine, or use one of your own, as long as the file name is img.jpg.



Tip: If your Internet connection is too fast to view, you can turn it down on Chrome’s DevTools Network.



The core API is the Progress event, and the event argument to its callback function has a total and loaded properties. Total Indicates the total size of resources, and loaded indicates the downloaded size. The units are bytes (B for short). Total/Loaded * 100 gives you the current progress in percentage units.

Cancel the upload/download option for the user

When the user finds the download speed is slow, the user can be given an option to cancel the download. Continue with the example above. We provide a stop download button and a re-download button. Refactoring index. HTML.

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>img</title>
  </head>
  <body>
    <div id="container"></div>
    <div id="procress"></div>
    <button id="cancel">Cancel the download</button>
    <button id="retry">To download</button>
    <script>
      const procress = document.getElementById("procress");
      const container = document.getElementById("container");
      const cancel = document.getElementById("cancel");
      const retry = document.getElementById("retry");

      let abort = null;

      function downloadImg() {
        const xhr = new XMLHttpRequest();
        xhr.open("GET"."http://localhost:3000/img");
        xhr.responseType = "blob";
        xhr.addEventListener("progress".(e) = > {
          procress.innerText = 'Loaded:The ${Math.floor(
            (e.loaded / e.total) * 100
          )}`;
        });
        xhr.addEventListener("load".(e) = > {
          const img = new Image();
          img.style.width = "100%";
          img.src = URL.createObjectURL(e.currentTarget.response);
          container.replaceWith(img);
        });
        xhr.addEventListener("abort".(e) = > {
          procress.innerText = "Download stopped";
        });
        xhr.send();
        return xhr.abort.bind(xhr);
      }

      abort = downloadImg();
      cancel.addEventListener("click".() = > abort());
      retry.addEventListener("click".() = > (abort = downloadImg()));
    </script>
  </body>
</html>
Copy the code

The core API is the Xhr. abort method. Note that the abort method is a native function and must be bound to XHR for this or you will get an Uncaught TypeError: Illegal Invocation.

Record some information when you close the page

Now consider a scenario where our website records the last time a user was online when they closed the page. In the early days, the solution was to listen for window unload events and then send an XHR request. But browsers don’t process asynchronous code when closing documents, so synchronous XHR must be used to delay the user closing the page until the server responds. This is not a good experience for the user. Here’s an example. Add an interface.

app.post("/offline".function (req, res) {
  setTimeout(() = > {
    console.log(req.body);
    res.send("");
  }, 3000);
});
Copy the code

Rewrite the index.html to synchronize requests using XHR at unload.

<html>
  <body>
    <div>Please turn me off!</div>
    <script>
      window.addEventListener(
        "unload".() = > {
          const data = JSON.stringify({ userID: "12345b" });
          localStorage.setItem("data", data);
          const xhr = new XMLHttpRequest();
          xhr.open("post"."http://localhost:3000/offline".false);
          xhr.setRequestHeader(
            "Content-Type"."application/json; charset=UTF-8"
          );
          xhr.send(data);
        },
        false
      );
    </script>
  </body>
</html>
Copy the code

However, XHR performing synchronization in unload events is no longer supported in many major browsers.

butlocalStorage.setItem("data", data);It’s in effect.

You can view the stored data in the Local Storage of the Application panel.



In addition to this solution, there is a more reliable and feasible solution, the sendBeacon API.

The sendBeacon API is very simple and can only accept two parameters, the first is the URL and the second is the data.

The default is a POST request. You can’t use JSON data. You can use ArrayBufferView, Blob, FormData, and String.

FormData is used here for data transfer.

Modified index. HTML.

<html>
  <body>
    <div>Please turn me off!</div>
    <script>
      window.addEventListener(
        "unload".() = > {
          let data = new FormData();
          data.append("userID"."12345b");
          // const data = JSON.stringify({ userID: "12345b" });
          navigator.sendBeacon("http://localhost:3000/offline", data);
        },
        false
      );
    </script>
  </body>
</html>

Copy the code

Since Express cannot directly parse formData’s data, a second middleware is needed.

npm i express-formidable
Copy the code

Add middleware.

var formidable = require("express-formidable");

app.use(formidable());
Copy the code

Example Modify an interface.

app.post("/offline".function (req, res) {
  setTimeout(() = > {
    console.log(req.fields);
    res.send("");
  }, 3000);
});
Copy the code

The Beacon does not execute immediately, but is queued asynchronously and waits to execute when the browser is idle.

Retry when the request fails

Let’s say the communication between the browser and the server is down due to our poor network, and we want to resend a request on failure and continue to retry until the retry succeeds. Add an interface.

app.get("/data".function (req, res) {
  const value = Math.random() * 100;
  if (value > 10) {
    res.status(500);
  }
  return res.send({ value });
});
Copy the code

The logic of the interface is to generate a random number ranging from 0 to 100. If the number is greater than 10, the HTTP status code is set to 500, indicating an internal server error. Modified index. HTML.

<html>
  <body>
    <div id="log"></div>
    <script>
      const log = document.getElementById("log");
      let count = 0;

      function getData() {
        const xhr = new XMLHttpRequest();
        xhr.open("get"."http://localhost:3000/data");
        xhr.responseType = "json";
        xhr.addEventListener("load".() = > {
          if(xhr.status ! = =200) {
            log.innerHTML = Request failed, retry in progress${count++}Retries `;
            setTimeout(getData, 1000);
            return;
          }
          log.innerHTML = 'The request succeeds and the data is:The ${JSON.stringify(xhr.response)}`;
        });
        xhr.send();
      }

      getData();
    </script>
  </body>
</html>
Copy the code

The logic of the index.html is to listen for the load event of XHR. If the value of xhr.status is not 200, the request is considered failed and the request is sent again one second later. Until the request succeeds. If xhr.status is not 200, it does not trigger XHR’s error event.

The significance of Ajax

The ultimate value Ajax brings is a huge increase in user experience. In terms of technology, the impact is equally huge, and it is the cornerstone of almost all modern front-end technology. It directly and indirectly promoted the birth and evolution of the front-end separation architecture, MVVM architecture and SPA (Single Page Application) architecture. Ajax is also one of the core technologies driving the rise of the Web wave. We can even argue that without Ajax, the Web might not be where it is today.

Fetch

In modern browsers, XHR is not the only option for sending HTTP requests. In addition to XHR, there is another API for sending HTTP requests in the browser native environment, called FETCH. As I mentioned earlier, ** Almost any new technology in computing is created to solve some of the problems of the old technology. ** So is XHR a perfect product? Obviously not. Nothing in a computer is perfect. Just as science in our human society is always moving forward, all the technologies we see now will one day be completely overturned.

The problem with XHR

So what’s the problem with XHR?

Does not comply with the SOC principle

There is a well-known principle in software design called Separation of concerns, or SOC. Only by following the SOC principle can a system with high cohesion and low coupling be designed. If you don’t understand the seemingly lofty terms SOC and high cohesion and low coupling, there’s no need to panic. They’re easy to understand. Focus, is what you want to do, focus on one thing, only do this one thing well, maintain a single responsibility. There are many modules in the system, each focused on doing its own thing. Eventually, many modules were put together to form a system. By doing so, there will be less coupling between modules and it will be easier to upgrade and refactor modules. This knowledge is also a fundamental principle of software design. It is no exaggeration to say that the technical strength of a programmer has little to do with how many apis he knows, how many frameworks he uses, how many projects he has done, how many lines of code he has written, how many titles he has, what famous company he works for, what important role he holds, how much money he earns per month, and so on. What really matters is the ability to design. Obviously, XHR does not comply with the separation of concerns (SOC) principle. Because its request, response, events, and everything else is on this object, XHR. It’s not hard to see that many of the interfaces in the BOM API and DOM API do not conform to SOC principles. They are like a complex mix of functions that are eventually mounted on an object, resulting in many Web apis having a large number of properties and methods that are unrelated to each other. It’s easy to see why. Back in the slash-and-burn days of software specification, Web standards makers weren’t able or necessary to design the kind of apis we require today.

The callback hell

In JavaScript, Callback Hell is a problem that has plagued many programmers. This experience is especially obvious to anyone who has written Node.js. Take the following code for example:

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ':' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this()}})}})Copy the code

The code is shaped like a pyramid rotated 90 degrees, with a lot of} and) at the end. If you’re not familiar with callback hell, check out the article in the link above. I’m not going to go into that. The problem with callback hell is that it is not concise enough and the code structure is difficult to understand and maintain. If you’re using XHR, it’s easy to write code that looks like callback hell.

The birth of the fetch

Because XHR is so widely used, standard setters can no longer make radical changes to XHR for compatibility reasons, except to extend some features. In this historical context, they decided to make a new API, and fetch was born. Many people’s first impression of Fetch is that it is a very new API. But actually FETCH has been around for 10 years. This, along with jQuery’s Ajax, came out in 2011. It was only in 2015 that FETCH was actually implemented by browsers. Why 2015? Because FETCH relies on the Promise API, it can’t be standardized until the Promise API is standardized through the proposal. The Promise API was standardized in ES2015, so fetch was standardized in the same year.

The characteristics of the fetch

Fetch is simpler to use than XHR, but it does not mean that FETCH is more advanced than XHR. Instead, FETCH is a low-level API. The usage is as follows:

fetch('https://api.github.com/users/luzhenqian')
    .then((response) = > {
        return response.json();
    })
		.then((data) = > {
  		console.log(data)
		})
    .catch(function (err) {
        console.log(err);
    });
Copy the code

Here is a brief explanation of the above code. Fetch is a function that is mounted to the Window object and can be used directly in a browser environment. The first parameter is the URL of the interface. You can ignore method; the default is GET. To set method, you need to pass a second parameter. Its return value is a Promise, and data and exceptions are handled through then and catch methods. Now let’s look at how FETCH solves two of XHR’s biggest problems.

Follow SOC principles

The Fetch API consists of one function and three interfaces. The values are FETCH, Headers, Request, and Response. Fetch is a function used to fetch resources. Headers indicates the request and response Headers that can be queried through this interface. Request Indicates a resource Request. Response indicates the Response to the request. Setting the request header looks like this:

let headers = new Headers();
headers.append('Content-Type'.'text/json');
let initConfig = { headers };

fetch('https://api.github.com/users/swapnilbangare', initConfig)
    .then(function (response) {
        return response.json();
    })
    .then(function (data) {
        console.log(data);
    })
    .catch(function (err) {
        console.log(err);
    });
Copy the code

The first argument to fetch can also pass a Request object. The argument to the then is a Response object. This design is a design that follows the PRINCIPLES of SOC.

Fix the callback hell

Since the FETCH returns a Promise object, there is naturally no problem with callback hell. Promise was designed as a solution to the callback hell of asynchronous callbacks, turning the code structure from a nested layer into a flat one. I don’t want to talk too much about Promises here, but if you’re interested, I recommend you check them out.

Reconstruct the TODO List using FETCH

The transformation process is relatively simple, as you only need to adjust the Add and getData functions.

function add() {
  const headers = new Headers({ "Content-type": "application/json" });
  const request = new Request("http://localhost:3000/todo", {
    method: "post",
    headers,
    body: JSON.stringify({ todo: todoInput.value }),
  });
  fetch(request)
    .then((response) = > response.json())
    .then((data) = > {
    if (data.status === "success") {
      todoInput.value = ""; getData(); }}); }function getData() {
  const request = new Request("http://localhost:3000/todo");
  fetch(request)
    .then((response) = > response.json())
    .then(renderList);
}
Copy the code

However, because the FETCH API is relatively new, some slightly older versions of browsers may not support FETCH, which is where polyfill is needed. Whatwg-fetch is recommended.

The disadvantage of the fetch

Fetch solves some of XHR’s problems, but it’s not perfect on its own. In the years since FETCH went from proposal to standardization, it has been the subject of intense community discussion. Two of the most criticized are:

  • Timeout interrupt
  • Progress of the event

Timeout interrupt

At the beginning of the FETCH, there is no timeout interrupt capability. After intense community discussion, AbortController finally approved the proposal. At present, AbortController and setTimeout are used to implement the timeout interrupt of fetch, which is rather tedious on the whole. The code is as follows:

const controller = new AbortController();
const signal = controller.signal;

setTimeout(() = > controller.abort(), 5000);

fetch(url, { signal })
Copy the code

Create a controller and give the request a signal. Set a delay to the controller’s abort method to break the request after the delay has passed.

Progress of the event

So far the FETCH has no events related to progress. This means that FETCH cannot implement XHR’s onProcess events. This is very deadly. However, it is not completely unsolved. Service Workers were used in the Fetch – Progress-Indicators project to solve the progress problem. However, there are some minor problems, such as when the page is cancelled, the file read by the network does not terminate. The abort callback on the Window and IMG tags is not triggered. We can only hope that the specification will solve this problem completely.

Axios

Axios is an Ajax library that can also be called an Http Client. Why do we need to learn this library when we’ve already learned the XHR and FETCH apis? Both XHR and FETCH can be used, but for their own reasons (XHR is a product of a previous era, and FETCH is low-level), we rarely use these two apis directly when doing engineering projects. Instead, you’ll use some encapsulated high-level API and functionality. Ajax libraries have sprung up over the past two decades, and some of the best early Web frameworks, such as YUI, Ext.js, and jQuery, have built-in Ajax modules. With the development of the Web, modern JavaScript frameworks and libraries are gradually moving in the direction of lightweight and functional specialization. In the specific field of Ajax, there are also many excellent Ajax libraries such as WHATWG-Fetch, Superagent and Cross-Fetch. Each library has a different focus. For example, WHATWG-FETCH is just a polyfill for fetch and does not encapsulate the API. The superAgent feature uses the Nodejs API to run the same code in the Nodejs environment as in the browser environment. Cross-fetch is cross-platform, with support for Nodejs, browsers, and React Native (a framework for developing mobile apps using JavaScript technology and the React framework). Why separate out Axios? Because Axios is by far the most popular, most popular, and actually the most used Ajax library is Undoubtedly Axios. Axios is almost the first item on an HTTP client if nothing else. The biggest feature of Axios is a set of apis that support both Nodejs and browser environments, a pattern known as isomorphism. This advantage is hard to appreciate in pure front-end development. In addition to isomorphism, Axios provides some common functions and features:

  • Automatically convert JSON data.
  • Support for the Promise API.
  • The interceptor.
  • The converter.
  • Error handling.
  • Defend against XSRF attacks.

These functions and features are very useful when working on an engineering project.

Refactor TODOList using Axios

The purpose of this article is not to explain how to use Axios. If you need more detailed Documentation of Axios, check out the official Axios website. Here, we refactor TODOList again using Axios to get a sense of how Axios is used. For ease of use, Axios is installed using CND. Add the CDN.

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
Copy the code

Refactor the add and getData methods in index.html.

function add() {
  const data = { todo: todoInput.value };
  axios.post("http://localhost:3000/todo", data).then((res) = > {
    if (res.data.status === "success") {
      todoInput.value = ""; getData(); }}); }function getData() {
  axios("http://localhost:3000/todo").then((res) = > {
    renderList(res.data);
  });
}
Copy the code

Here you can see the advantage of following the SOF principle to write a program. Each function can only do one thing. When we refactor the program, we only need to change the part that needs to be refactored. The above example shows just the most basic use of Axios. If you want to learn more about Axios, it is highly recommended that you read the official documentation of Axios. The documentation is very simple and easy to read. There is really nothing better to learn than the official documentation of Axios, and I don’t want to do it any more.

HTTP Basics

Since Ajax makes asynchronous requests over the HTTP protocol, it’s important to learn some HTTP basics. If you’re familiar with HTTP, you can skip this section. If you are not familiar with HTTP, this section is a supplement.

Difference between GET and POST

This is a very classic interview questions, whether front-end engineers, back-end engineers, network engineers, are basically required to ask. Although it is simple and elementary. But it must be a classic for a reason. This question covers a wide range of areas, so it will give you an indication of your overall understanding of HTTP. The Web has gone through many eras, and so has HTTP, one of the core components of the Web world. HTTP 0.9 supports only the GET method. HTTP 1.0 supports get, HEAD, and POST methods. HTTP 1.1 supports nine methods. The vast majority of clients currently use http1.1. HTTP, though, was originally intended only for specific scenarios where browsers and servers communicate. But today, HTTP is no longer just for communicating between a browser and a server. Servers can also communicate with each other using THE HTTP protocol. Here only for the browser and server communication this case. Just two sentences. Get is used to retrieve data, has no side effects, is idempotent, can be cached, and the request parameters are placed in the queryString of the URL. Post is used to modify data, has side effects, is not idempotent, and cannot be cached. Request data can be placed in the queryString of the URL, or in the body. There are some design terms: side effect, idempotence, browser cache, etc. Here’s a quick explanation.

Side effects/idempotence

Side effects are changes to the data while the program is running. Idempotent means that repeated results are always the same, which can also be interpreted as idempotent if there are no side effects, and idempotent if there are side effects. The following function is side-effect-free/idempotent.

function add(a, b) {
  return a + b;
}
Copy the code

No matter how many times you call it, the result is always the same.

add(1.2)/ / 3
add(1.2)/ / 3
add(1.2)/ / 3
add(1.2)/ / 3
// ...
Copy the code

The following function is side-effect/non-idempotent.

var c = 1;
function add2(a, b) {
  return a + b + c++;
}
Copy the code

Because it depends on the external C, the number of calls gets bigger and bigger.

add2(1.2)/ / 4
add2(1.2)/ / 5
add2(1.2)/ / 6
add2(1.2)/ / 7
// ...
Copy the code

Browser cache

Browser caching refers to the local storage of data requested by the browser.

Each request is first checked for a local cache, and if so, the locally cached data is returned without actually reaching the server.

The cache will expire for a certain period of time, at which point the cache will expire, and the request will reach the server again to retrieve the latest data and update the local cache.

The general process can be viewed as follows:

Of course, there’s more to browser caching strategies than that, and more details are beyond the scope of this article, so you can learn about them if you’re interested.

Other common methods are introduced

All methods defined in HTTP are viewed in the RFC2616 method definitions. There are eight types: Options, GET, HEAD, POST, PUT, DELETE, Trace, and CONNECT. Later, patch method was added to PUT method in RFC5789, so there are altogether 9 methods at present. Here’s a quick overview of what they do: There are five of the most common methods. Get is used to obtain data, POST is used to add data, patch is used to modify partial data, PUT is used to modify complete data, and delete is used to delete data. The other four methods are used less often. Head is similar to GET, but does not return the body of the message, only the header. Options is usually used for forwarding. Trace is usually used to get the message loop. Connect Dynamically switches to the proxy server of the tunnel. These methods can be divided into two categories, one is safe, the other is idempotent methods. Secure methods are methods that do nothing but fetch data. Only get and head are safe methods. Idempotent methods mean that the side effects of N requests are the same as those of a single request. Get, HEAD, PUT, DELETE, Options, and Trace are all idempotent methods.

Classification of common HTTP status codes

Recorded in theRFC2616 status code and cause phraseThere are 40 HTTP status codes in WebDAV (RFC4918,RFC5842) and attach the HTTP status code (RFC6585), the number of more than 60 species.

Of course, we don’t need to memorize all of them, just a few common status codes and rules.

1xx indicates the connection is in progress, 2xx indicates success, 3xx indicates redirection, 4xx indicates an error on the client, and 5xx indicates an error on the server.

Go to the link above when you encounter other situations.

Or check out the chart below.

conclusion

In this article, from DOM forms to JavaScript XHR to FETCH to Ajax libraries like Axios, we’ve seen that technology is constantly evolving and new technologies are evolving. And what drives it all is necessity. Necessity is the mother of all inventions.

practice

By this point, I think you’ve learned something. If you didn’t get anything out of my article, you’ve reached a very high level of understanding and application of Ajax.

It’s not enough just to read the article, it’s just to learn the knowledge. You also need the ability to translate that knowledge into your own. So, in addition to the various code examples in this article, you need to do some practice.

You can try using Ajax to implement an image upload component with progress hints.

Here is an image upload component I implemented.



I’ve already written the server interface so you can use it directly:

const Koa = require("koa");
const Router = require("@koa/router");
const cors = require("@koa/cors");
const multer = require("@koa/multer");

const app = new Koa();
const router = new Router();
const upload = multer();

app.use(cors());

router.post(
  "/upload",
  upload.fields([{ name: "file".maxCount: 1024 * 1024 }]),
  (ctx) = > {
    ctx.body = "done"; }); app.use(router.routes()); app.use(router.allowedMethods()); app.listen(9002);
Copy the code

Not that it is not implemented using Express, it is implemented using the KOA technology stack, you need to install the corresponding dependencies.

npm i koa @koa/router @koa/cors @koa/multer
Copy the code