用 promises 实现的 debounce 函数

新手上路,请多包涵

我正在尝试实现一个与 javascript 中的承诺一起使用的去抖功能。这样,每个调用者都可以使用 Promise 使用“去抖动”函数的结果。这是迄今为止我能想到的最好的:

 function debounce(inner, ms = 0) {
  let timer = null;
  let promise = null;
  const events = new EventEmitter();  // do I really need this?

  return function (...args) {
    if (timer == null) {
      promise = new Promise(resolve => {
        events.once('done', resolve);
      });
    } else {
      clearTimeout(timer);
    }

    timer = setTimeout(() => {
      events.emit('done', inner(...args));
      timer = null;
    }, ms);

    return promise;
  };
}

理想情况下,我想 在不 引入对 EventEmitter 的依赖(或实现我自己的 EventEmitter 的基本版本)的情况下实现此实用程序功能,但我想不出一种方法来实现它。有什么想法吗?

原文由 Chris 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 857
2 个回答

我找到了一个更好的方法来实现这个承诺:

 function debounce(inner, ms = 0) {
  let timer = null;
  let resolves = [];

  return function (...args) {
    // Run the function after a certain amount of time
    clearTimeout(timer);
    timer = setTimeout(() => {
      // Get the result of the inner function, then apply it to the resolve function of
      // each promise that has been created since the last time the inner function was run
      let result = inner(...args);
      resolves.forEach(r => r(result));
      resolves = [];
    }, ms);

    return new Promise(r => resolves.push(r));
  };
}

我仍然欢迎提出建议,但新的实现回答了我最初关于如何在不依赖 EventEmitter(或类似的东西)的情况下实现此功能的问题。

原文由 Chris 发布,翻译遵循 CC BY-SA 3.0 许可协议

在 Chris 的解决方案中,所有调用都将在它们之间延迟解决,这很好,但有时我们只需要解决最后一个调用。

在我的实现中,只会解析间隔中的最后一次调用。

 function debounce(f, interval) {
  let timer = null;

  return (...args) => {
    clearTimeout(timer);
    return new Promise((resolve) => {
      timer = setTimeout(
        () => resolve(f(...args)),
        interval,
      );
    });
  };
}

并且以下 typescript(>=4.5) 实现支持中止的功能:

  1. 支持 通过 reject() 中止承诺。如果我们不中止它,它就不能执行 finally 函数。
  2. 支持 自定义拒绝 abortValue
    • 如果我们捕获错误,我们可能需要确定错误类型是否是 Aborted
 /**
 *
 * @param f callback
 * @param wait milliseconds
 * @param abortValue if has abortValue, promise will reject it if
 * @returns Promise
 */
export function debouncePromise<T extends (...args: any[]) => any>(
  fn: T,
  wait: number,
  abortValue: any = undefined,
) {
  let cancel = () => { };
  // type Awaited<T> = T extends PromiseLike<infer U> ? U : T
  type ReturnT = Awaited<ReturnType<T>>;
  const wrapFunc = (...args: Parameters<T>): Promise<ReturnT> => {
    cancel();
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => resolve(fn(...args)), wait);
      cancel = () => {
        clearTimeout(timer);
        if (abortValue!==undefined) {
          reject(abortValue);
        }
      };
    });
  };
  return wrapFunc;
}

/**
// deno run src/utils/perf.ts
function add(a: number) {
  return Promise.resolve(a + 1);
}
const wrapFn= debouncePromise(add, 500, 'Aborted');

wrapFn(2).then(console.log).catch(console.log).finally(()=>console.log('final-clean')); // Aborted + final-clean
wrapFn(3).then(console.log).catch(console.log).finally(()=>console.log('final-clean')); // 4 + final_clean

笔记:

  • 我做了一些内存基准测试,大量 未决的承诺不会导致内存泄漏。似乎 V8 引擎 GC 会清除未使用的承诺。

原文由 Николай Гордеев 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题