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 });
        };
    })
相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试