Summary: real-time search will face a common problem, that is, the browser requests the back-end interface to be asynchronous. If the requested interface is initiated first and then the data is returned, the data displayed in the list/table is likely to be messy.

This article is shared from the HUAWEI CLOUD community " How to solve the problem of data errors caused by uneven speed of asynchronous interface requests? ", original author: Kagol.

introduction

Search function, I think many businesses will be involved, the characteristics of this function are:

  • The user can enter a keyword in the input box, and then display the data corresponding to the keyword in a list;
  • The input box can modify/delete all or part of the keywords at any time;
  • If it is a real-time search (that is, the result will be displayed immediately after entering the keyword, no additional operation or excessive waiting is required), the interface call will be very frequent.

Real-time search will face a common problem, that is:

The browser requests backend interfaces are asynchronous. If the requested interface is initiated first and then the data is returned, the data displayed in the list/table is likely to be messy.

Reproduce the problem

Recently, the test raised a search (PS: the search here is implemented with the newly launched CategorySearch component of DevUI) related defect list, which involves the above problems.
image.png

The general meaning of this bug list is:

When searching, if you input or delete keywords in quick succession, the search results do not match the search keywords.

Judging from the screenshot of the defect list, the original intention is to search for the keyword 8.4.7 iteration], and the actual search result in the table is the data that has passed the keyword 8.4.7 iteration.

The screenshot of the defect list also posted two requested information very intimately:
image.png

As an "experienced" front-end developer, at first glance it is a general technical problem:

  1. Browser requests from the server are asynchronous;
  2. Since the previous request from the server was slow to return, the next request was initiated before the first request returned the result, and the result was returned quickly, at this time the table must display the result of the next time;
  3. After 2 seconds, the result of the first request was returned slowly. At this time, the form incorrectly displayed the result of the first request;
  4. Eventually led to this bug.

How to solve it?

Before thinking of a solution, you have to find a way to find this problem. It is unrealistic to rely on the background interface. In most cases, the background interface will quickly return results.

Therefore, if this problem is necessary, a slow interface must be simulated first.

Simulate slow interface

In order to quickly build a background service and simulate a slow interface, we choose Koa, a lightweight Node framework.

Quick start

Koa is very convenient to use, just:

  1. New project folder: mkdir koa-server
  2. Create package.json: npm init -y
  3. Install Koa: npm i koa
  4. Write service code: vi app.js
  5. Start: node app.js
  6. Visit: http://localhost :3000/

Write service code

Use the following command to create the app.js startup file:

vi app.js
Enter the following 3 lines of code in the file to start a Koa service:

const Koa = require('koa'); // 引入 Koa
const app = new Koa(); // 创建 Koa 实例
app.listen(3000); // 监听 3000 端口

access

If the task service is not started on port 3000, visit in the browser:

http://localhost:3000/

The following page will be displayed:
image.png

After starting our Koa Server, visit:

http://localhost:3000/

Will show:
image.png

get request

What I just built is just an empty service, there are no routes, so Not Found is displayed.

We can make our Koa Server show something through middleware.

Since we want to add a root route, we first install the route dependency

npm i koa-router
Then introduce Koa Router

const router = require('koa-router')();
Next is to write the get interface

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

Finally, don't forget to use routing middleware

app.use(router.routes());
After changing the code, you need to restart the Koa service. In order to facilitate restarting, we use pm2, a Node process management tool, to start/restart the Koa service. It is also very simple to use:

  • Install pm2 globally: npm i -g pm2
  • Start Koa Server: pm2 start app.js
  • Restart Koa Server: pm2 restart app.js

After restarting Koa Server, visit again

http://localhost:3000/

The following will be displayed:
image.png

post request

With the above foundation, you can write a post interface to simulate a slow interface!

Writing the post interface is very similar to the get interface:

router.post('/getList', async (ctx, next) => {
  ctx.response.body = {
    status: 200,
    msg: '这是post接口返回的测试数据',
    data: [1, 2, 3]
  };
});

At this time, we can use Postman to call the post interface and return as expected:
image.png

Allow cross-domain

We try to call this post interface in the NG CLI project:

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

But when you call it directly in the browser, you can't get the desired result:

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

image.png

Since the project port number (4200) of the locally launched project is different from the Koa Server (3000), the browser thinks this interface is cross-domain, so it is blocked.

Local link of NG CLI project:

http://localhost:4200/

Koa Server link:

http://localhost:3000/

Koa has a middleware that allows cross-domain: koa2-cors

The use of this middleware is very similar to routing middleware.

Install dependencies first:

npm i koa2-cors
Then introduce:

const cors = require('koa2-cors');
Use middleware again:

app.use(cors());
At this time, we will visit again:

http://localhost:4200/

You can get the result you want!
image.png

Slow interface

The post interface already exists, how to simulate a slow interface?

In fact, it is hoped that the server will delay the return of the result.

Add the logic of delay before the post interface:

async function delay(time) {
    return new Promise(function(resolve, reject) { 
      setTimeout(function() {
        resolve();
      }, time);
    });
  }
  await delay(5000); // 延迟 5s 返回结果
  ctx.response.body = { ... };

Visit the getList interface again and find that the previous interface will always be pending, and it will take more than 5 seconds to actually return the result.
image.png
image.png

Cancel slow interface request

If you can simulate a slow interface, you can easily test the questions!

This problem must be discovered first, and then try to fix it. Finally, we can see if the problem does not appear. If it does not appear, it means that our solution can solve the bug. The problem also means that we have to think of other ways.

This is the correct way to open the bug fix.

The most intuitive solution is to initiate the second request, if the first request does not return, then directly cancel this request and use the returned result of the second request.

How to cancel an http request?

Angular's asynchronous event mechanism is based on RxJS, and it is very convenient to cancel an ongoing http request.

We have seen earlier that Angular uses the HttpClient service to initiate http requests, and calls the subscribe method to subscribe to the returned results in the background:

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

To cancel the http request, we need to save the subscription to a variable in the component:

private getListSubscription: Subscription;

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

Then, before re-initiating the http request, cancel the subscription for the last request.

this.getListSubscription?.unsubscribe(); // 重新发起 http 请求之前,取消上一次请求的订阅

this.getListSubscription = this.http.post(...);

How other http libraries cancel requests

So far this defect is solved. In fact, this is a general problem. No matter what business or framework you use, you will encounter data confusion problems caused by slow asynchronous interfaces.

So, if you use the native http request interface of the browser like fetch or the http library widely used in the industry like axios, how to cancel the ongoing http request?

fetch

Let's first look at fetch. Fetch is an AJAX interface natively provided by the browser, and it is also very convenient to use.

Use fetch to initiate a post request:

fetch('http://localhost:3000/getList', {
   method: 'POST',
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  },
  body: JSON.stringify({
    id: 1
  })
}).then(result => {
  console.log('result', result);
});

You can use AbortController to achieve request cancellation:

this.controller?.abort(); // 重新发起 http 请求之前,取消上一次请求

const controller = new AbortController(); //  创建 AbortController 实例
const signal = controller.signal;
this.controller = controller;

fetch('http://localhost:3000/getList', {
   method: 'POST',
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  },
  body: JSON.stringify({
    id: 1
  }),
  signal, // 信号参数,用来控制 http 请求的执行
}).then(result => {
  console.log('result', result);
});

axios

Let's take a look at axios, let's take a look at how to use axios to initiate a post request.

Install first:

npm i axios
Introduce again:

import axios from 'axios';
Initiate 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);
});

The request initiated by axios can be cancelled by cancelToken.

this.source?.cancel('The request is canceled!');

this.source = axios.CancelToken.source(); // 初始化 source 对象

axios.post('http://localhost:3000/getList', {
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  },
  data: {
    id: 1,
  },
}, { // 注意是第三个参数
  cancelToken: this.source.token, // 这里声明的 cancelToken 其实相当于是一个标记或者信号
})
.then(result => {
  console.log('result:', result);
});

summary

This article summarizes the common methods of defect analysis and resolution through the problems encountered in the actual project, and conducts an in-depth analysis of the data error problem caused by asynchronous interface requests.

Click to follow, and get to know the fresh technology of


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量