摘要:实时搜寻都会面临一个通用的问题,就是浏览器申请后盾接口都是异步的,如果先发动申请的接口后返回数据,列表/表格中显示的数据就很可能会是错乱的。

本文分享自华为云社区《如何解决异步接口申请快慢不均导致的数据谬误问题?》,原文作者:Kagol 。

引言

搜寻性能,我想很多业务都会波及,这个性能的特点是:

  • 用户能够在输入框中输出一个关键字,而后在一个列表中显示该关键字对应的数据;
  • 输入框是能够随时批改/删除全副或局部关键字的;
  • 如果是实时搜寻 (即输出完关键字马上出后果,不须要额定的操作或过多的期待),接口调用将会十分频繁。

实时搜寻都会面临一个通用的问题,就是:

浏览器申请后盾接口都是异步的,如果先发动申请的接口后返回数据,列表/表格中显示的数据就很可能会是错乱的。

问题重现

最近测试提了一个搜寻(PS:此处的搜寻 就是用 DevUI 新推出的 CategorySearch 组件实现的)相干的缺点单,就波及到了上述问题。

这个bug单大抵意思是:

搜寻的时候,间断疾速输出或者删除关键字,搜寻后果和搜寻关键字不匹配。

从缺点单的截图来看,本意是要搜寻关键字8.4.7迭代】,表格中的理论搜寻后果是8.4.7迭代】过关键字的数据。

缺点单的截图还十分贴心地贴了两次申请的信息:

作为一名“有教训的”前端开发,一看就是一个通用的技术问题:

  1. 浏览器从服务器发动的申请都是异步的;
  2. 因为前一次申请服务器返回比较慢,还没等第一次申请返回后果,后一次申请就发动了,并且迅速返回了后果,这时表格必定显示后一次的后果;
  3. 过了2秒,第一次申请的后果才慢悠悠地返回了,这时表格谬误地又显示了第一次申请的后果;
  4. 最终导致了这个bug。

怎么解决呢?

在想解决方案之前,得想方法必现这个问题,靠后盾接口是不事实的,大部分状况下后盾接口都会很快返回后果。

所以要必现这个问题,得先模仿慢接口。

模仿慢接口

为了疾速搭建一个后盾服务,并模仿慢接口,咱们抉择 Koa 这个轻量的 Node 框架。

疾速开始

Koa 应用起来十分不便,只须要:

  1. 新建我的项目文件夹:mkdir koa-server
  2. 创立 package.json:npm init -y
  3. 装置 Koa:npm i koa
  4. 编写服务代码:vi app.js
  5. 启动:node app.js
  6. 拜访:http://localhost:3000/

编写服务代码

应用以下命令创立 app.js 启动文件:

vi app.js
在文件中输出以下 3 行代码,即可启动一个 Koa 服务:

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

拜访

如果没有在3000端口启动工作服务,在浏览器拜访:

http://localhost:3000/

会显示以下页面:

启动了咱们的 Koa Server 之后,拜访:

http://localhost:3000/

会显示:

get 申请

方才搭建的只是一个空服务,什么路由都没有,所以显示了Not Found。

咱们能够通过中间件的形式,让咱们的 Koa Server 显示点儿货色。

因为要减少一个根路由,咱们先装置路由依赖

npm i koa-router
而后引入 Koa Router

const router = require('koa-router')();
接着是编写get接口

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

最初别忘了应用路由中间件

app.use(router.routes());
改完代码须要重启 Koa 服务,为了不便重启,咱们应用 pm2 这个 Node 过程管理工具来启动/重启 Koa 服务,应用起来也非常简单:

  • 全局装置 pm2:npm i -g pm2
  • 启动 Koa Server:pm2 start app.js
  • 重启 Koa Server:pm2 restart app.js

重启完 Koa Server,再次拜访

http://localhost:3000/

会显示以下内容:

post 申请

有了以上根底,就能够写一个 post 接口,模仿慢接口啦!

编写 post 接口和 get 接口很相似:

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

这时咱们能够应用 Postman 调用下这个 post 接口,如期返回:

容许跨域

咱们尝试在 NG CLI 我的项目里调用这个 post 接口:

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

然而在浏览器里间接调用,却得不到想要的后果:

  • result 没有打印进去
  • 控制台报错
  • Network申请也是红色的

因为本地启动的我的项目端口号(4200)和 Koa Server 的(3000)不同,浏览器认为这个接口跨域,因而拦挡了。

NG CLI 我的项目本地链接:

http://localhost:4200/

Koa Server 链接:

http://localhost:3000/

Koa 有一个中间件能够容许跨域:koa2-cors

这个中间件的应用形式,和路由中间件很相似。

先装置依赖:

npm i koa2-cors
而后引入:

const cors = require('koa2-cors');
再应用中间件:

app.use(cors());
这时咱们再去拜访:

http://localhost:4200/

就能失去想要的后果啦!

慢接口

post 接口曾经有了,怎么模仿慢接口呢?

其实就是心愿服务器提早返回后果。

在 post 接口之前减少提早的逻辑:

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

再次拜访 getList 接口,发现后面接口会始终pending,5s 多才真正返回后果。

勾销慢接口申请

能模仿慢接口,就能轻易地必现测试提的问题啦!

先必现这个问题,而后尝试修复这个问题,最初看下这个问题还出不呈现,不呈现阐明咱们的计划能解决这个bug,问题还有阐明咱们得想别的方法。

这是修复bug正确的打开方式。

最直观的计划就是再发动第二次申请之后,如果第一次申请未返回,那就间接勾销这次申请,应用第二次申请的返回后果。

怎么勾销一次http申请呢?

Angular 的异步事件机制是基于 RxJS 的,勾销一个正在执行的 http 申请十分不便。

后面曾经看到 Angular 应用 HttpClient 服务来发动 http 申请,并调用subscribe 办法来订阅后盾的返回后果:

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

要勾销 http 申请,咱们须要先把这个订阅存到组件一个变量里:

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

而后在从新发动 http 申请之前,勾销上一次申请的订阅即可。

this.getListSubscription?.unsubscribe(); // 从新发动 http 申请之前,勾销上一次申请的订阅this.getListSubscription = this.http.post(...);

其余 http 库如何勾销申请

至此这个缺点算是解决了,其实这是一个通用的问题,不论是在什么业务,应用什么框架,都会遇到异步接口慢导致的数据错乱问题。

那么,如果应用 fetch 这种浏览器原生的 http 申请接口或者 axios 这种业界宽泛应用的 http 库,怎么勾销正在进行的 http 申请呢?

fetch

先来看下 fetch,fetch 是浏览器原生提供的 AJAX 接口,应用起来也十分不便。

应用 fetch 发动一个 post 申请:

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);});

能够应用 AbortController 来实现申请勾销:

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

再来看看 axios,先看下如何应用 axios 发动 post 申请。

先装置:

npm i axios
再引入:

import axios from 'axios';
发动 post 申请:

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

axios 发动的申请能够通过 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);});

小结

本文通过理论我的项目中遇到的问题,总结缺点剖析和解决的通用办法,并对异步接口申请导致的数据谬误问题进行了深刻的解析。

点击关注,第一工夫理解华为云陈腐技术~