关于angular:技术实践丨如何解决异步接口请求快慢不均导致的数据错误问题

42次阅读

共计 4830 个字符,预计需要花费 13 分钟才能阅读完成。

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

本文分享自华为云社区《如何解决异步接口申请快慢不均导致的数据谬误问题?》,原文作者: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'); // 引入 Koa
const 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);
});

小结

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

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

正文完
 0