DevUI 是一款面向企业中后台产品的开源前端解决方案,它倡导沉浸
、灵活
、至简
的设计价值观,提倡设计者为真实的需求服务,为多数人的设计,拒绝哗众取宠、取悦眼球的设计。若是你正在开发 ToB
的工具类产品
,DevUI 将是一个很不错的选择!前端
搜索功能,我想不少业务都会涉及,这个功能的特色是:node
实时搜索都会面临一个通用的问题,就是:ios
浏览器请求后台接口都是异步的,若是先发起请求的接口后返回数据,列表/表格中显示的数据就极可能会是错乱的。
最近测试提了一个搜索(PS:此处的搜索🔍就是用 DevUI 新推出的 CategorySearch 组件实现的)相关的缺陷单,就涉及到了上述问题。npm
这个bug单大体意思是:json
搜索的时候,连续快速输入或者删除关键字,搜索结果和搜索关键字不匹配。
从缺陷单的截图来看,本意是要搜索关键字8.4.7迭代】
,表格中的实际搜索结果是8.4.7迭代】过
关键字的数据。axios
缺陷单的截图还很是贴心地贴了两次请求的信息:segmentfault
做为一名“有经验的”前端开发,一看就是一个通用的技术问题:跨域
怎么解决呢?浏览器
在想解决方案以前,得想办法必现这个问题,靠后台接口是不现实的,大部分状况下后台接口都会很快返回结果。服务器
因此要必现这个问题,得先模拟慢接口。
为了快速搭建一个后台服务,并模拟慢接口,咱们选择 Koa 这个轻量的 Node 框架。
Koa 使用起来很是方便,只须要:
mkdir koa-server
npm init -y
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端口启动任务服务,在浏览器访问:
会显示如下页面:
启动了咱们的 Koa Server 以后,访问:
会显示:
刚才搭建的只是一个空服务,什么路由都没有,因此显示了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 服务,使用起来也很是简单:
重启完 Koa Server,再次访问
会显示如下内容:
有了以上基础,就能够写一个 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); });
可是在浏览器里直接调用,却得不到想要的结果:
因为本地启动的项目端口号(4200)和 Koa Server 的(3000)不一样,浏览器认为这个接口跨域,所以拦截了。
NG CLI 项目本地连接:
Koa Server 连接:
Koa 有一个中间件能够容许跨域:koa2-cors
这个中间件的使用方式,和路由中间件很相似。
先安装依赖:
npm i koa2-cors
而后引入:
const cors = require('koa2-cors');
再使用中间件:
app.use(cors());
这时咱们再去访问:
就能获得想要的结果啦!
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(...);
至此这个缺陷算是解决了,其实这是一个通用的问题,不论是在什么业务,使用什么框架,都会遇到异步接口慢致使的数据错乱问题。
那么,若是使用 fetch 这种浏览器原生的 http 请求接口或者 axios 这种业界普遍使用的 http 库,怎么取消正在进行的 http 请求呢?
先来看下 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 发起 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); });
本文经过实际项目中遇到的问题,总结缺陷分析和解决的通用方法,并对异步接口请求致使的数据错误问题进行了深刻的解析。
咱们是DevUI团队,欢迎来这里和咱们一块儿打造优雅高效的人机设计/研发体系。招聘邮箱:muyang2@huawei.com。
文/DevUI Kagol
往期文章推荐