摘要: 实时搜寻都会面临一个通用的问题,就是浏览器申请后盾接口都是异步的,如果先发动申请的接口后返回数据,列表 / 表格中显示的数据就很可能会是错乱的。
本文分享自华为云社区《如何解决异步接口申请快慢不均导致的数据谬误问题?》,原文作者:Kagol。
引言
搜寻性能,我想很多业务都会波及,这个性能的特点是:
- 用户能够在输入框中输出一个关键字,而后在一个列表中显示该关键字对应的数据;
- 输入框是能够随时批改 / 删除全副或局部关键字的;
- 如果是实时搜寻(即输出完关键字马上出后果,不须要额定的操作或过多的期待),接口调用将会十分频繁。
实时搜寻都会面临一个通用的问题,就是:
浏览器申请后盾接口都是异步的,如果先发动申请的接口后返回数据,列表 / 表格中显示的数据就很可能会是错乱的。
问题重现
最近测试提了一个搜寻(PS:此处的搜寻 就是用 DevUI 新推出的 CategorySearch 组件实现的)相干的缺点单,就波及到了上述问题。
这个 bug 单大抵意思是:
搜寻的时候,间断疾速输出或者删除关键字,搜寻后果和搜寻关键字不匹配。
从缺点单的截图来看,本意是要搜寻关键字 8.4.7 迭代】,表格中的理论搜寻后果是 8.4.7 迭代】过关键字的数据。
缺点单的截图还十分贴心地贴了两次申请的信息:
作为一名“有教训的”前端开发,一看就是一个通用的技术问题:
- 浏览器从服务器发动的申请都是异步的;
- 因为前一次申请服务器返回比较慢,还没等第一次申请返回后果,后一次申请就发动了,并且迅速返回了后果,这时表格必定显示后一次的后果;
- 过了 2 秒,第一次申请的后果才慢悠悠地返回了,这时表格谬误地又显示了第一次申请的后果;
- 最终导致了这个 bug。
怎么解决呢?
在想解决方案之前,得想方法必现这个问题,靠后盾接口是不事实的,大部分状况下后盾接口都会很快返回后果。
所以要必现这个问题,得先模仿慢接口。
模仿慢接口
为了疾速搭建一个后盾服务,并模仿慢接口,咱们抉择 Koa 这个轻量的 Node 框架。
疾速开始
Koa 应用起来十分不便,只须要:
- 新建我的项目文件夹:mkdir koa-server
- 创立 package.json:npm init -y
- 装置 Koa:npm i koa
- 编写服务代码:vi app.js
- 启动:node app.js
- 拜访: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);
});
小结
本文通过理论我的项目中遇到的问题,总结缺点剖析和解决的通用办法,并对异步接口申请导致的数据谬误问题进行了深刻的解析。
点击关注,第一工夫理解华为云陈腐技术~