一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情。
前情提要
在项目开发中难免会遇到即时请求的情况。比如联系人搜索框,需要进行模糊搜索,需要实现用户输入完成后立马查询接口
问题:
- 需要时刻监听用户的输入动作,然后每监听到一次就发一次请求。结果会发现请求太多引发性能问题,
- 异步请求会导致老的请求结果可能比新的请求结果返回的慢,导致最新结果被老结果覆盖。
解决方案:
- 防抖:利用
闭包。设定 监听到用户动作后N秒后发起请求,在这个时间段内每监听到一次用户动作就重置一次等待时间,直到在等待时间内没有再监听到用户的输入动作后再发起请求。 - 利用axios的
CancelToken:创建一个对象,每次发起请求就以该请求的请求路径+请求方法为键,CancelToken返回的实例为值。在请求拦截器中,每拦截到一个请求就进行对比,如果存在对象中则执行对象中对应的CancelToken进行取消上次未完成的请求,然后继续。在响应拦截器中,数据正常请求后就将该次请求从对象中删除。 - 关于防抖在这里就不做过多的介绍了,会有专门的文章进行介绍。
代码
- /utils/axios.js
- 为了方便阅读,不展示不相关的代码
import axios from 'axios';
const service = axios.create({})
let pending = {}; // 展示对象
/**
* @description 用于取消指定请求并清除对象中该属性。或者只清除该对象中的此属性
* @param { * } config 请求对象体
* @param { boolean } status 请求是否已经完成
* @return { void }
*/
const removePending = (config, status) => {
const { url, method } = config;
const key = `${ url }&{ method }`
if (pending.hasOwnProperty(key) && !status) pending[key]();
delete pending[key]
}
// 请求拦截器:所有的请求在发起请求之初都会走这个
service.interceptors.request.use(
async (config) => {
const { url, method } = config;
const key = `${ url }&${ method }`;
// 先检查该请求是否存在pending对象中。如果存在则清除上次的请求
pending.hasOwnProperty(key) && removePending(config, false);
// 将本次请求添加到pending对象中
config.cancelToken = new axios.CancelToken((f) => {
pending[key] = f;
})
return config;
},
(error) => {
return Promise.reject(error);
}
)
// 响应拦截器:所有的请求在响应之后都会走这个
service.interceptors.response.use(
(response) => {
const { config } = response
removePending(config, false); // 请求成功删除pending对象中的该请求属性
return response;
},
(error) => {
return Promise.reject(error);
}
)
// 最后将service对象抛出去就行
export default service;
抛出问题
- 此时有可能会遇到一种业务场景,那就是一个页面可能存在一个接口被多次调用,仅仅是参数不同,所获取的数据分别要展示在不同的位置。那么此时上面的处理便会让该场景出现异常。
- 处理方案:
- 自行在封装接口的时候增加一个自定义属性:isUsePending
- 部分代码如下:
// 请求拦截器:所有的请求在发起请求之初都会走这个
service.interceptors.request.use(
async (config) => {
if (config.isUsePending) {
const { url, method } = config;
const key = `${ url }&${ method }`;
// 先检查该请求是否存在pending对象中。如果存在则清除上次的请求
pending.hasOwnProperty(key) && removePending(config, false);
// 将本次请求添加到pending对象中
config.cancelToken = new axios.CancelToken((f) => {
pending[key] = f;
})
}
return config;
},
(error) => {
return Promise.reject(error);
}
)
// 响应拦截器:所有的请求在响应之后都会走这个
service.interceptors.response.use(
(response) => {
const { config } = response
config.isUsePending && removePending(config, false); // 请求成功删除pending对象中的该请求属性
return response;
},
(error) => {
return Promise.reject(error);
}
)
// 最后将service对象抛出去就行
export default service;