实现一个以 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博客

相关推荐
neter.asia2 分钟前
vue中如何关闭eslint检测?
前端·javascript·vue.js
~甲壳虫3 分钟前
说说webpack中常见的Plugin?解决了什么问题?
前端·webpack·node.js
光影少年22 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_23 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891125 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾26 分钟前
前端基础-html-注册界面
前端·算法·html
Rattenking26 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
Dragon Wu28 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym33 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫34 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js