前言
众所周知,axios是一款使用十分广泛的JS请求库,为我们封装好了非常多的东西。
但是不包括前端较为常见的防抖和节流,那么如果需要的话,我们该怎么做呢?
本文提供一个简单的示例与思路给到大家参考!
防抖和节流的简介
防抖: 用户的连续相同的操作如果间隔不大于某个值(防抖的间隔),则只生效一次。
常见的防抖:用户输入框打字,内容在短时间内不停变化,如果需要监听内容的变化,最好加上防抖,提高性能。
节流: 用户在多个时间段内(节流的间隔)进行了多次相同的操作,在每个时间段内只生效一次。
常见的节流:窗口大小resize更新某些内容时,可以使用节流提升性能。
总的来说,防抖和节流就是为了使回调函数调用不那么频繁,以此提升性能。
封装思路
对于防抖和节流,一个很重要的步骤就是要识别重复操作。
对于普通的场景来说,重复的操作可能是用同样的参数调用同样的函数。
对于axios 来说,重复的操作就是用重复的参数发起重复的请求,所以我们第一点就是需要识别重复请求。
对于请求,最重要的几个参数就是:url 、params 、data,如果这些参数全部重复,那么就可以认为发起了重复的请求。
甚至还可以更为无脑粗暴,将axios 所有请求的options进行比较,如果一致,就当作重复。
调用设计
封装思路已经大致理清楚,接下来就是设计我们的请求调用方式,参考经典lodash 的debounce 和throttle方法,设计如下形式的调用:
js
request({
url:'/api',
method:'get',
params:{name:'lsm'},
debounce:{
wait:200,
// leading:false,
// trailing:true,
// maxWait:9999
}
})
request({
url:'/api',
method:'post',
data:{name:'lsm'},
throttle:{
wait:200,
// leading:false,
// trailing:true
}
})
出于简单,本次的示例就不用到leading 、trailing 等参数,就只用到wait。
支持异步的debounceAsync
由于lodash 提供的debounce 方法如果回调函数是异步的,并不能获得异步函数的结果,所以我们需要写一个支持异步的debounceASync方法
js
function debounceAsync(fn, wait) {
let timeout = null;
return function (...args) {
return new Promise((resolve, reject) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
try {
// 执行原函数,并假设它返回一个Promise
const result = fn(...args);
if (result instanceof Promise) {
// 如果是Promise,则解析这个Promise
result.then(resolve).catch(reject);
} else {
// 如果不是Promise,则直接解析结果
resolve(result);
}
} catch (error) {
// 如果执行原函数时发生错误,则拒绝Promise
reject(error);
}
}, wait);
});
};
}
throttle 方法也是同理,也需要写一个支持异步的throttleAsync方法,此处我就不提供了,哈哈!
request函数
js
import axios from 'axios'
import {debounceAsync,throttleAsync} from './util'
// 存放重复请求方法
const debounceMap = new Map()
const throttleMap = new Map()
function request(options) {
// 正常情况下的请求,调用axios
let method = () => {
return new Promise((resolve, reject) => {
axios(options)
.then(res => resolve(res))
.catch(err => reject(err))
})
}
const { debounce, throttle } = options
// 将请求参数转为请求唯一标识key
const key = JSON.stringify(options)
// 如果开启了防抖
if (debounce?.wait) {
// 判断是否已经存在相同的请求
if (debounceMap.has(key)) {
// 存在相同的请求,直接从map中获取
console.log('存在相同请求');
method = debounceMap.get(key)
} else {
// 不存在相同的请求,使用debounceAsync构造一个方法
console.log('不存在相同请求');
const { wait, leading = false, trailing = true, maxWait = 9999 } = debounce;
// 构造防抖请求
method = debounceAsync(method, wait, { leading, trailing, maxWait })
// 将该请求放入map中
debounceMap.set(key, method);
}
return method()
}
// 如果开启了节流,与防抖同理
if (throttle?.wait) {
// 判断是否已经存在相同的请求
if (throttleMap.has(key)) {
method = throttleMap.get(key)
} else {
const { wait, leading = false, trailing = true } = throttle;
// 构造防抖请求
method = throttleAsync(method, wait, { leading, trailing })
// 将该请求放入map中
throttleMap.set(key, method);
}
return method()
}
// 正常情况下
return method()
}
防抖测试
我们启动一个间隔100毫秒执行一次的调度,在一秒后清除该调度,由于debounce 的wait设置的时间是200毫秒,正确情况下只会有最后一次请求正常发起并且返回。
js
const timer = setInterval(() => {
request({
url: 'https://ipapi.co/json',
method: 'get',
debounce: {
wait: 200
}
}).then(res => {
console.log('成功', res.data);
}).catch(err => {
console.log('失败', err.message);
})
}, 100);
setTimeout(() => {
clearInterval(timer)
}, 1000);
执行结果如下:
yaml
不存在相同请求
存在相同请求
存在相同请求
存在相同请求
存在相同请求
存在相同请求
存在相同请求
存在相同请求
存在相同请求
成功 {
ip: '**********',
network: '*********',
version: 'IPv4',
city: 'Xiamen',
region: 'Fujian',
region_code: 'FJ',
country: 'CN',
country_name: 'China',
country_code: 'CN',
country_code_iso3: 'CHN',
country_capital: 'Beijing',
country_tld: '.cn',
continent_code: 'AS',
in_eu: false,
postal: null,
latitude: 24.4793,
longitude: 118.0673,
timezone: 'Asia/Shanghai',
utc_offset: '+0800',
country_calling_code: '+86',
currency: 'CNY',
currency_name: 'Yuan Renminbi',
languages: 'zh-CN,yue,wuu,dta,ug,za',
country_area: 9596960,
country_population: 1411778724,
asn: 'AS4809',
org: 'China Telecom Next Generation Carrier Network'
}
优化
我们还可以给request函数补充一些简写方法:
js
['get', 'post', 'put', 'delete'].forEach((method) => {
request[method] = function (options) {
return request({ ...options, method });
};
})