Abstract: Real-time search will face a common problem, that is, the browser request background interface is asynchronous, if the interface initiates the request first and then returns data, the data displayed in the list/table is likely to be corrupted.

This document is shared with huawei cloud community. How can I Solve the Data Error Problem Caused by Uneven Asynchronous Interface Request Speeds? , by Kagol.

The introduction

The search function, which I think will be involved in many businesses, is characterized by:

  • Users can enter a keyword in the input box, and then display the corresponding data of the keyword in a list.
  • Input box can be modified/deleted at any time all or part of the keyword;
  • If you search in real time (that is, you get results as soon as you enter a keyword, without extra action or too much waiting), the interface calls will be very frequent.

Real-time search faces a common problem, which is:

The browser requests the backend interface asynchronously, and if the interface initiates the request first and then returns data, the data displayed in the list/table is likely to be corrupted.

Problem reproduction

A recent test raised a bug sheet related to a search (PS: the search here is implemented using DevUI’s new CategorySearch component) that addresses the above issue.

This bug simply means:

When a user enters or deletes a keyword rapidly, the search result does not match the keyword.

From the screenshot of the defect sheet, it is intended to search for the keyword 8.4.7 iteration 】. The actual search result in the table is 8.4.7 iteration 】 data with the keyword.

The screenshot of the bug list also helpfully includes two requests:

As an “experienced” front-end developer, this appears to be a general technical problem:

  1. Requests made by the browser from the server are asynchronous;
  2. Due to the slow return of the previous request server, the second request was initiated before the result of the first request was returned, and the result was quickly returned. At this time, the table must show the result of the second request.
  3. After two seconds, the results of the first request slowly return, at which point the table mistakenly shows the results of the first request again;
  4. And that led to this bug.

How do you solve it?

Before trying to find a solution, you have to find a way to solve this problem. It is not practical to rely on the background interface. In most cases, the background interface will return results quickly.

So to solve this problem, you have to simulate the slow interface first.

Analog slow interface

To quickly build a backend service and simulate a slow interface, we chose Koa, a lightweight Node framework.

Quick start

Koa is very easy to use and requires only:

  1. Create a project folder: mkdir koa-server
  2. Create package.json: NPM init -y
  3. Install Koa: NPM I Koa
  4. Write the service code: vi app.js
  5. Start: Node app.js
  6. Visit: http://localhost:3000/

Writing service code

Create the app.js startup file with the following command:

vi app.js
Copy the code

To start a Koa service, enter the following three lines of code in the file:

const Koa = require('koa'); // add Koa const app = new Koa(); // Create Koa instance app.listen(3000); // Listen on port 3000Copy the code

access

If the task service is not started on port 3000, access it in a browser:

http://localhost:3000/

The following page is displayed:

After starting our Koa Server, visit:

http://localhost:3000/

Will display:

A get request

“Not Found” is displayed because there is no route for an empty service.

We can use middleware to make our Koa Server show something.

Since we want to add a root route, we install route dependencies first

npm i koa-router
Copy the code

Then introduce the Koa Router

const router = require('koa-router')();
Copy the code

Next, write the GET interface

app.get('/', async (ctx, next) => { ctx.response.body = '<p>Hello Koa Server! </p>'; });Copy the code

Finally, don’t forget to use routing middleware

app.use(router.routes());
Copy the code

The pm2 Node process management tool is used to start/restart the Koa service. It is also very simple to use:

  • Global installation PM2: NPM I-g PM2
  • Start Koa Server: pm2 start app.js
  • Run the pm2 restart app.js command to restart the Koa Server

Restart the Koa Server and try again

http://localhost:3000/

The following will be displayed:

A post request

With this foundation, you can write a POST interface to simulate the slow interface.

Writing a POST interface is similar to writing a GET interface:

Router. Post ('/getList', async (CTX, next) => {ctx.response.body = {status: 200, MSG: 'This is the test data returned by post ', data: [1, 2, 3]}; });Copy the code

We can then call the POST interface with Postman and return as expected:

Allow cross-domain

We tried calling the POST interface in the NG CLI project:

this.http.post('http://localhost:3000/getList', {
  id: 1,
}).subscribe(result => {
  console.log('result:', result);
});
Copy the code

But when you call it directly from the browser, you don’t get the desired result:

  • Result is not printed
  • Console error
  • Network requests are also red

Because the project port number started locally (4200) is different from Koa Server’s (3000), the browser thinks this interface is cross-domain and blocks it.

NG CLI project local link:

http://localhost:4200/

Koa Server Link:

http://localhost:3000/

Koa has one middleware that allows cross-domain: KOA2-CORS

This middleware is used in a similar way to routing middleware.

Install dependencies first:

npm i koa2-cors
Copy the code

Then introduce:

const cors = require('koa2-cors');
Copy the code

Re-use middleware:

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

Then we go to visit:

http://localhost:4200/

You can get the result you want!

Slow interface

I already have a POST interface. How do I simulate a slow interface?

You want the server to delay the return of results.

Add delay logic before POST interface:

async function delay(time) { return new Promise(function(resolve, reject) { setTimeout(function() { resolve(); }, time); }); } await delay(5000); Ctx.response.body = {... };Copy the code

When I access the getList interface again, I find that the previous interface is always pending, and it takes more than 5s to return the result.

Cancel the slow interface request

Can simulate the slow interface, you can easily test the question!

First, we must present this problem, and then try to fix it. Finally, we will see if this problem still appears. If it does not appear, our solution can solve this bug, and the problem also means that we have to think of other ways.

This is the correct opening to fix the bug.

The most intuitive solution is to make a second request, and if the first request does not return, just cancel the request and use the result of the second request.

How do I cancel an HTTP request?

Angular’s asynchronous event mechanism is based on RxJS, making it very convenient to cancel an ongoing HTTP request.

We’ve seen that Angular uses the HttpClient service to make HTTP requests and calls the subscribe method to subscribe to the background result:

this.http.post('http://localhost:3000/getList', {
  id: 1,
}).subscribe(result => {
  console.log('result:', result);
});
Copy the code

To cancel the HTTP request, we need to store the subscription in a component variable:

private getListSubscription: Subscription;

this.getListSubscription = this.http.post('http://localhost:3000/getList', {
  id: 1,
}).subscribe(result => {
  console.log('result:', result);
});
Copy the code

Then unsubscribe from the previous request before re-initiating the HTTP request.

this.getListSubscription? .unsubscribe(); This.getlistsubscription = this.http.post(...) ;Copy the code

How do other HTTP libraries cancel requests

At this point, this defect is solved, in fact, this is a common problem, no matter what business, use what framework, will encounter asynchronous interface slow data corruption problem.

So how do you cancel an ongoing HTTP request using fetch, a browser-native HTTP request interface, or AXIos, an industry-wide HTTP library?

fetch

Take a look at FETCH, which is an AJAX interface provided natively by the browser and very easy to use.

Use fetch to make a POST request:

Fetch (' http://localhost:3000/getList '{method: "POST", headers: {' the content-type' : 'application/json. Charset = UTF-8 '}, body: json.stringify ({id: 1})}). Then (result => {console.log('result', result); });Copy the code

AbortController can be used to cancel the request:

this.controller? .abort(); Const Controller = new AbortController(); // create AbortController instance const signal = controller.signal; this.controller = controller; Fetch (' http://localhost:3000/getList '{method: "POST", headers: {' the content-type' : 'application/json. Charset = utF-8 '}, body: json.stringify ({id: }). Then (result => {console.log('result', result); });Copy the code

axios

Let’s take a look at Axios and see how you can use Axios to initiate a POST request.

First installation:

npm i axios
Copy the code

Introduction:

import axios from 'axios';
Copy the code

Make a POST request:

axios.post('http://localhost:3000/getList', { headers: { 'Content-Type': 'application/json; charset=utf-8' }, data: { id: 1, }, }) .then(result => { console.log('result:', result); });Copy the code

A request made by Axios can be cancelled by cancelToken.

this.source? .cancel('The request is canceled! '); this.source = axios.CancelToken.source(); / / initialization of the source object axios. Post (' http://localhost:3000/getList '{headers: {' the content-type' : 'application/json. Charset =utf-8'}, data: {id: 1,},}, {// }). Then (result => {console.log('result:', result); });Copy the code

summary

Through the problems encountered in the actual project, this paper summarizes the general method of defect analysis and solution, and deeply analyzes the data error problem caused by asynchronous interface request.

Click to follow, the first time to learn about Huawei cloud fresh technology ~