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

相关推荐
LCG元1 小时前
【面试问题】JIT 是什么?和 JVM 什么关系?
面试·职场和发展
迷雾漫步者1 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-1 小时前
验证码机制
前端·后端
燃先生._.2 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖3 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235243 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240254 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar4 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人5 小时前
前端知识补充—CSS
前端·css