防抖功能的逐步递进

前言

我们开发中可能遇到形形色色的业务,其中会用到防抖节流,节流我们这里先不考虑,我们对于防抖功能通过一步一步的探索,并研究出针对其更加有效的处理方案,也能让我们在合适的场景用合适的防抖逻辑

防抖探索

模拟高响应差的网络请求

先模拟一个接口请求,但是响应时间随机,大约在0~3000ms之间,方便用于模拟高响应差下的连续请求情况

原因:两个不同搜索条件差距巨大,在庞大的数据量下,请求时间波动可能会略大,这里进一步扩大这种特殊情况

js 复制代码
//模拟一个 3s 以内的随机请求相应时间的接口请求
const queryRecords = (text) => {
  let res, rej;
  const promise = new Promise((resolve, reject) => {
    res = resolve;
    rej = reject;
  });
  const interval = new Date().getTime() % 3000;
  setTimeout(() => {
    if (interval > 2990) {
      rej("接口太累了,超时了");
    } else {
      res(text);
    }
  }, interval);
  return promise;
};

模拟一个基础搜索请求

一个基础的请求是这样的,一般场景这么写很ok

场景:点击搜索按钮请求,点一次请求一次

js 复制代码
//默认的搜索逻辑-待优化
const search = async (text) => {
  const res = await queryRecords(text);
  console.log("默认的搜索逻辑-待优化", res);
};
// search("初版本");

模拟防抖场景

有很多场景没有搜索按钮发挥的地方,或者带搜索按钮影响体验,因此为了增加体验,我们决定让用户边输入边请求,这样频繁调用接口肯定不合适,所以会用到防抖功能,大约用户输入暂停后500ms后发起请求

场景:首页的菜单搜索,本身地方有限,带着搜索按钮看着也不优雅,这种逻辑一般用于搜索时间相对稳定的场景,尤其是针对于本地拥有全部搜索数据的情况

使用,对搜索方法包装debounce防抖处理,调用防抖函数,并且本案例还模拟了多次请求场景(且模拟卡着500ms延迟边缘请求,用于后续发现问题)

js 复制代码
//为了增加体验,我们的搜索决定,让用户输入后自动搜索,500ms无操作则请求接口
//因此加入的防抖功能,延迟 500ms,自动取消之前未发起的请求
const debounce = (fn, interval) => {
  let timeout = null;
  return (text) => {
    timeout && clearTimeout(timeout);
    timeout = setTimeout(() => {
      fn(text);
      timeout = null;
    }, interval);
  };
};

const searchWithDebounce = async (text) => {
  const res = await queryRecords(text);
  console.log("防抖搜索,基础版本", res);
};

const realSearch = debounce(searchWithDebounce, 500);
//进行如下测试,会发现,如果搜索时间比较长,切搜索时间不稳定,则可能出现后请求的先响应
//1. 假设防抖 300ms执行了第一个请求,此时又搜索了一个内容,假设第二个内容600ms后执行
//2. 如果后执行的任务速度快先响应(也就是后发先至),则搜索后的响应展示,则可能会展示已经失效的搜索内容(第一次搜索的内容,但内容已过期)
const testRealSearch = () => {
  let i = 0;
  let interval = setInterval(() => {
    realSearch(i++);
    if (i > 10) {
      clearInterval(interval);
    }
  }, 500);
};

// testRealSearch()

针对接口高响应差的防抖逻辑处理

通过上面留下的案例,也可以看出来,正常防抖,面对耗时比较久,且根据不同搜索条件响应时间比较久的情况下,会出现响应错误问题,也就是最后响应的是先发起的请求,处理不当的话,可能会引起前端页面渲染错误的场景出现

因此进行下面两种优化:

  1. 外部存放一个时间戳,每次发起请求前,更新时间戳,以最后一次发起请求的时间戳进行响应,其他的均忽略
  2. 编写一个通用的请求控制逻辑接管请求promise函数,保证只响应最后一次发起的请求(算是1的通用封装版本,更实用)

步骤1,简易的利用发起的时间戳对比响应逻辑

js 复制代码
//上面的防抖逻辑问题正常快速响应的情况下,或者搜索时间比较稳定的情况问题不大,一旦波动则会出现请求后发先至问题,从而响应错误
//1.正常情况,我们做个请求时间标记,只处理最新发起的一次请求即可,逻辑也比较简单
//打印表现,发起n->改进回调n(中间可能会有多个每次回调,但不能有新的发起),则表示一次正常有效回调
let currentDate = null;
const searchWithDebounce2 = async (text) => {
  const date = new Date();
  currentDate = date;
  console.log("防抖搜索-优化版本1-发起请求", text);
  await queryRecords(text);
  console.log("防抖搜索-优化版本1-上版每次回调", text);
  if (date === currentDate) {
    console.log("防抖搜索-优化版本1-改进后实际回调", text);
  }
};
const realSearch2 = debounce(searchWithDebounce2, 500);
const testRealSearch2 = () => {
  let i = 0;
  let interval = setInterval(() => {
    realSearch2(i++);
    if (i > 10) {
      clearInterval(interval);
    }
  }, 600);
};
// testRealSearch2()

步骤2,通过接管请求函数的方式,封装时间戳对比逻辑,封装成统一的逻辑

js 复制代码
//2.通用实现,我们做一个通用逻辑,直接传入一个请求,直接控制请求,不写那么多重复的currentDate之类的判断代码了
const singleRequest = (fn) => {
  let currentDate = null;
  return (...args) => {
    const now = new Date();
    currentDate = now;
    let res, rej;
    const newPromise = new Promise((resolve, reject) => {
      res = resolve;
      rej = reject;
    });
    //接管传入的请求的promise,控制promise的响应
    fn(...args)
      .then((result) => {
        currentDate === now && res(result);
      })
      .catch((error) => {
        currentDate === now && rej(error);
      });
    return newPromise;
  };
};

//接管请求
const newRecordsRequest = singleRequest(queryRecords);
const searchWithDebounce3 = async (text) => {
  const res = await newRecordsRequest(text);
  console.log("防抖搜索-优化版本2(接口级别控制)", res);
};
const realSearch3 = debounce(searchWithDebounce3, 500);
const testRealSearch3 = () => {
  let i = 0;
  let interval = setInterval(() => {
    realSearch3(i++);
    if (i > 10) {
      clearInterval(interval);
    }
  }, 600);
};
testRealSearch3();

最后

通过上面也可以看出,对于搜索逻辑,功能总是一步一步完善,只有考虑到更多的场景,才能进一步完善我们的程序,有些场景甚至基本不太可能出现,但存在一定的可能,我们就应该考虑到,这能大幅度提升我们的应用体验,减少偶先bug的可能,也是提高我们技术和阅历的机会😄

相关推荐
剪刀石头布啊1 小时前
对象的大小比较与面向对象思考
前端
最爱老式锅包肉3 小时前
《HarmonyOS技术精讲-ArkWeb》桥接两岸:JSBridge原生与Web互调
前端·华为·harmonyos
IT_陈寒4 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
踩着两条虫4 小时前
可视化 vs 终端 vs 云端:VTJ.PRO、Claude Code、Codex 三强横评
前端·vue.js·人工智能·低代码·架构
kyriewen4 小时前
Godot 全面封杀 Vibe Coding,作为每天用 AI 写代码的人,我反而觉得这是件好事
前端·ai编程·claude
前进的搬砖er5 小时前
4.three.js的几何体、纹理、光源、材质、uv属性 讲义
前端
前端毕业班5 小时前
uni-app 小程序支持 teleport 了
前端·javascript·vue.js
梨子同志5 小时前
qiankun
前端
梨子同志5 小时前
Nginx
前端