Axios请求防抖与节流简易封装

前言

众所周知,axios是一款使用十分广泛的JS请求库,为我们封装好了非常多的东西。

但是不包括前端较为常见的防抖和节流,那么如果需要的话,我们该怎么做呢?

本文提供一个简单的示例与思路给到大家参考!

防抖和节流的简介

防抖: 用户的连续相同的操作如果间隔不大于某个值(防抖的间隔),则只生效一次。

常见的防抖:用户输入框打字,内容在短时间内不停变化,如果需要监听内容的变化,最好加上防抖,提高性能。

节流: 用户在多个时间段内(节流的间隔)进行了多次相同的操作,在每个时间段内只生效一次。

常见的节流:窗口大小resize更新某些内容时,可以使用节流提升性能。

总的来说,防抖和节流就是为了使回调函数调用不那么频繁,以此提升性能。

封装思路

对于防抖和节流,一个很重要的步骤就是要识别重复操作

对于普通的场景来说,重复的操作可能是用同样的参数调用同样的函数

对于axios 来说,重复的操作就是用重复的参数发起重复的请求,所以我们第一点就是需要识别重复请求。

对于请求,最重要的几个参数就是:urlparamsdata,如果这些参数全部重复,那么就可以认为发起了重复的请求。

甚至还可以更为无脑粗暴,将axios 所有请求的options进行比较,如果一致,就当作重复。

调用设计

封装思路已经大致理清楚,接下来就是设计我们的请求调用方式,参考经典lodashdebouncethrottle方法,设计如下形式的调用:

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
        }
    })

出于简单,本次的示例就不用到leadingtrailing 等参数,就只用到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毫秒执行一次的调度,在一秒后清除该调度,由于debouncewait设置的时间是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 });
        };
    })
相关推荐
仰望星空的小猴子15 分钟前
React18和React19新特性
前端
小码哥_常16 分钟前
Android新航标:Navigation 3为何成为变革先锋?
前端
SuperEugene17 分钟前
Vue状态管理扫盲篇:状态管理中的常见坑 | 循环依赖、状态污染与调试技巧
前端·vue.js·面试
骑着小黑马18 分钟前
从 Electron 到 Tauri 2:我用 3.5MB 做了个音乐播放器
前端·vue.js·typescript
aykon18 分钟前
DataSource详解以及优势
前端
Mintopia19 分钟前
戴了 30 天智能手环后,我才发现自己一直低估了“睡眠”
前端
leolee1819 分钟前
react redux 简单使用
前端·react.js·redux
仰望星空的小猴子20 分钟前
常用的Hooks
前端
天才熊猫君20 分钟前
Vue Fragment 锚点机制
前端
米丘21 分钟前
Git 常用操作命令
前端