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 });
        };
    })
相关推荐
AI大模型-小华7 分钟前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子2 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅2 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni3 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学4 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师4 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc885 小时前
微软EDGE浏览器功能学习
前端·学习·edge
Csvn5 小时前
🎯 原生 `<dialog>` 元素:终于可以扔掉一半的自定义弹窗组件了?
前端
Csvn5 小时前
🎨 CSS @layer:用「层叠优先级」终结样式打架的世纪难题
前端