【问题标题】:Debounce a function with argument带参数的函数去抖动
【发布时间】:2023-08-25 18:51:01
【问题描述】:

我正在尝试debounce 一个save 函数,该函数将对象保存为在击键时触发的自动保存的参数。 debounce 会阻止保存的发生,直到用户停止输入,或者至少是这样的想法。比如:

var save = _.debounce(function(obj) {
  ... 
}, delay);

如果我尝试快速连续保存两个对象,这就会崩溃。因为去抖动没有考虑传入的对象,所以只有第二次调用 save 会触发并且只有一个对象会被保存。

save(obj1);
save(obj2);

例如,只会保存obj2

我可以使obj 成为一个类的实例,该类具有自己的save 方法,该方法负责将去抖动保存到该对象。或者在某处保留一个部分/咖喱函数列表,但我希望那里有一个一站式解决方案。类似的东西:

var save = _.easySolution(function(obj) {
  ... 
}, delay);

我正在寻找以下保存字符串来保存每个对象,但每个对象只保存一次。

save(obj1);
save(obj2);
save(obj3);
save(obj2);
save(obj2);
save(obj3);
save(obj2);
save(obj1);

编辑:像这样的东西,也许只是不那么复杂,并且不会用__save 函数改变obj 的东西?

function save(obj) {
  if(!obj.__save) {
    obj.__save = _.debounce(_.partial(function(obj) {
      ...
    }, obj), delay);
  }

  obj.__save();
}

【问题讨论】:

  • 连读两遍也无法得到问题,但似乎你想要.bind()
  • 听起来你实际上并不想去抖动。
  • @zerkms 试图澄清。
  • @FelixKling 这有点像反跳。使用去抖动,如果大量的保存被快速连续触发(obj1。
  • 所以你需要每个对象去抖动?它实际上看起来像是 RxJS(或任何其他反应式编程库)与 .Distinct() 运算符结合 debounce 的工作。 (其实并没有我刚才说的那么简单,不过我觉得还是直接用Rx)

标签: javascript underscore.js lodash


【解决方案1】:

您将要为 get 传递的每个参数创建函数的去抖动版本。您可以使用debounce()memoize()wrap() 轻松做到这一点:

function save(obj) {
    console.log('saving', obj.name);
}

const saveDebounced = _.wrap(
    _.memoize(() => _.debounce(save), _.property('id')),
    (getMemoizedFunc, obj) => getMemoizedFunc(obj)(obj)
)

saveDebounced({ id: 1, name: 'Jim' });
saveDebounced({ id: 2, name: 'Jane' });
saveDebounced({ id: 1, name: 'James' });
// → saving James
// → saving Jane

您可以看到 'Jim' 没有保存,因为具有相同 ID 的对象随后被保存。 saveDebounced()函数分解如下。

_memoize() 的调用正在缓存基于某些解析器函数的去抖动 函数。在此示例中,我们只是将其基于 id 属性。所以现在我们有一种方法可以为任何给定的参数获取save() 的去抖动版本。这是最重要的部分,因为debounce() 具有各种内部状态,因此对于可能传递给save() 的任何参数,我们都需要此函数的唯一实例。

我们使用wrap() 调用缓存函数(或者如果是第一次调用,则创建它然后缓存它),并将函数传递给我们的对象。生成的saveDebounced() 函数与save() 具有完全相同的签名。不同之处在于saveDebounced() 将根据参数去抖动调用。


注意:如果你想以更通用的方式使用它,你可以使用这个辅助函数:

const debounceByParam = (targetFunc, resolver, ...debounceParams) =>
    _.wrap(
        _.memoize(
            () => _.debounce(targetFunc, ...debounceParams),
            resolver
        ),
        (getMemoizedFunc, ...params) =>
            getMemoizedFunc(...params)(...params)
    )

// And use it like so
function save(obj) {
    console.log('saving', obj.name);
}

const saveDebounced = debounceByParam(save, _.property('id'), 100)

【讨论】:

  • 我喜欢 memoize,但这与我需要的相反。我希望最后一次调用保存在给定的时间范围内运行,而不是第一次。然后更多的保存在未来运行。想象一下 save 是通过 xhr 请求向服务器发布内容。目的是让服务器在尽可能多的调用中保持对 obj 的最新更改。
  • 我不确定我是否在做与 nicholas 不同的事情,但是,对我来说,saveDebounced 只是保存了最后一个要传递的对象.. 完美。
  • @nicholas 这个答案完全符合您的要求。 debounced 函数 被记忆,但保存动作本身仍然是 debounce。顺便说一句,这个答案也适用于下划线。
【解决方案2】:

可能是这样的:

var previouslySeen = {}

var save = _.debounce(function(obj) {
  var key = JSON.stringify(obj);
  if (!previouslySeen[key]) {
     previouslySeen[key] = 1;
  } else {
     ...
  }
}, delay);

【讨论】:

  • 你试过用一些任意的对象吗?提示:它不起作用。你见过使用对象作为属性键的代码吗?
  • 你是对的。我修改了代码以对密钥进行字符串化。
【解决方案3】:

您可以在闭包中使用内部对象来设置/获取去抖动功能。

在这个例子中,我们在调用 debounced 函数时检查带有这个 args 的 debounced 函数是否已经保存在我们的 memory 对象中。如果没有 - 我们创建它。

const getDebouncedByType = (func, wait, options) => {
  const memory = {};

  return (...args) => {
    // use first argument as a key
    // its possible to use all args as a key - e.g JSON.stringify(args) or hash(args)
    const [type] = args;

    if (typeof memory[searchType] === 'function') {
      return memory[searchType](...args);
    }

    memory[searchType] = debounce(func, wait, { ...options, leading: true }); // leading required for return promise
    return memory[searchType](...args);
  };
}; 

原始要点 - https://gist.github.com/nzvtrk/1a444cdf6a86a5a6e6d6a34f0db19065

【讨论】: