实现一个以 Promise 状态判断的 throttle

起因

最近碰到一个面试题

页面里面有一个按钮,点击之后需要向服务端发起一个请求,在这个请求没有返回之前,无论点击多少次都不会发送第二遍请求。

基础写法

typescript 复制代码
const fetchData: (
  duration: number,
  status: "success" | "error"
) => Promise<"success" | "error"> = (duration, status = "success") => {
  console.log('Enter FetchData Function');
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      status === "success" ? resolve(status) : reject(status);
    }, duration);
  });
};


let lock = false;
const baseButtonDom = document.getElementById("base") as HTMLButtonElement;
const onClickFunc = async () => {
  if (!lock) {
    lock = true;
    const data = await fetchData(3000, "success");
    console.log(data);
    lock = false;
  }
};
baseButtonDom.addEventListener("click", onClickFunc);

如果只针对这个需求的话,是可以满足的,但是缺点也很明显,这个写法不够通用。

采用 Lodash 的防抖节流

先看看 他们的异同点

相同点:

  • 都可以通过使用 setTimeout 实现
  • 目的都是,降低回调执行频率。节省计算资源

不同点:

实现 执行时机
防抖 在一段连续操作结束后,处理回调,利用clearTimeoutsetTimeout实现 一定时间连续触发的事件,只在最后执行一次
节流 在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能 一段时间内只执行一次
javascript 复制代码
import { debounce, throttle } from "lodash";

const clickCallback = async () => {
  const data = await fetchData(3000, "success");
  console.log(data);
};
const debounceButtonDom = document.getElementById("debounce") as HTMLButtonElement;
const throttleButtonDom = document.getElementById("throttle") as HTMLButtonElement;
throttleButtonDom.addEventListener(
  "click",
  throttle(clickCallback, 3000, {
    leading: false, // 指定调用在节流开始前
  })
);
debounceButtonDom.addEventListener("click", debounce(clickCallback, 3000));

在快速点击三下的场景下,防抖和节流的打印表现都是一致的

在当连续点击的场景下会有所不同

  • 防抖 : 由于 防抖 会一直重置事件,所以会一直卡着不会进行输出,直到放开鼠标才会执行事件。
  • 节流 : 节流的实现逻辑是在指定时间内只会实现一次,但在连续点击时,只要超过设置的时间,就会进行下一次调用,没办法满足需求 " 在请求没有返回之前,无论点击多少次都不会发送第二遍请求。 "

所以 防抖和节流 达不到预期想要的结果;

最终实现

采用节流的逻辑实现,修改他的判断条件即可。节流是在规定时间内只执行一次,那么把这个条件修改成 Promise 状态判断就可以满足。pending 状态阻止调用,fulfilled 状态可以继续调用。那么函数 重新执行 的时机就是 fulfilled 的时候。也就满足了需求中的 " 在请求没有返回之前,无论点击多少次都不会发送第二遍请求。 "

typescript 复制代码
// 获取 Promise Status
const promiseState = (promise: Promise<unknown>) => {
  const target = {};
  return Promise.race([promise, target]).then(
    (value) => (value === target ? "pending" : "fulfilled"),
    () => "rejected"
  );
};
// 判读是否是 Promise
const isPromise = (promise: Promise<unknown>) => {
  return promise && promise.then && typeof promise.then === "function";
}

/**
 * 
 * @param {Asyncfunction} fn - 传入一个异步方法 
 * @returns {function}
 */
export const throttleForPromise = (fn: Function) => {
  let status: Promise<unknown> | null;
  return function (this: any) {
    if (status && isPromise(status)) {
      promiseState(status).then((resultStatus) => {
        if (resultStatus === "fulfilled") status = fn.apply(this, arguments);
      });
    } else {
      status = fn.apply(this, arguments);
    }
  };
};
  • 为什么不用 instanceof 判断 Promise ?
    instanceof 只能判断符合 ES 标准的 Promise ,不考虑兼容 Promise A+ 规范的的情况下完全可以
  • 还有其他办法拿到 Promise 的状态吗 ?
    Promise 的状态是一个内部状态,暂时没有办法直接拿到,如果有其他更好的获取方式的话,望告知
typescript 复制代码
const clickCallback = async () => {
  try {
    const data = await fetchData(3000, "success");
    console.log(data);
  } catch (error) {
    console.log(error);
  }
};
const promiseButtonDom = document.getElementById("throttle-promise") as HTMLButtonElement;
promiseButtonDom.addEventListener("click", throttleForPromise(clickCallback));

连续点击三下

连续不停点击 且 状态为 reject 时

均可以满足在 Promise 状态为 pending 时阻止继续调用,状态为 fulfilled 时重新执行。

以上 ,就是 throttlePromise 函数的简单实现了。函数只针对该需求的一个扩展,如果有更好的写法,欢迎讨论。

参考

在JavaScript获取Promise的状态_获取promise状态-CSDN博客

相关推荐
Raink老师11 小时前
【AI面试临阵磨枪-79】实时数据 RAG:订单、商家、物流、天气、动态库存
人工智能·面试·职场和发展
Cosolar11 小时前
Chroma向量库面试学习指南
数据库·人工智能·面试·职场和发展·数据库架构
Csvn12 小时前
OpenSpec 详细使用教程
前端
之歆13 小时前
Day19_LESS 完全指南——从入门到工程实践
前端·css·less
小江的记录本13 小时前
【JVM虚拟机】垃圾回收GC:垃圾收集器:CMS:核心原理、回收流程、优缺点、废弃原因(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·spring·面试·maven
云水一下13 小时前
HTML5 从入门到精通:实战收官——从零搭建完整静态网站,综合运用所有知识
前端·html5
不总是14 小时前
Windows 系统 Node.js 免安装版(zip)安装与配置教程(2026 最新)
前端·windows·node.js
冬奇Lab14 小时前
每日一个开源项目(第105篇):Twenty - 跳出 Salesforce 的圈套,定义现代开源 CRM
前端·后端·开源
zhangyao94033014 小时前
开发pc端时,表格的高度怎么设置才能铺满页面
前端·javascript·elementui
小江的记录本15 小时前
【JVM虚拟机】垃圾回收GC:垃圾回收算法:标记-清除、标记-复制、标记-整理、分代收集(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·算法·安全·面试