Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debounce a function with argument

I'm trying to debounce a save function that takes the object to be saved as a parameter for an auto-save that fires on keystroke. The debounce stops the save from happening until the user stops typing, or at least that's the idea. Something like:

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

Where this falls apart is if I try to save two objects in quick succession. Because the debounce doesn't take the passed in object into account, only the second call to save will fire and only one object will be saved.

save(obj1);
save(obj2);

Will only save obj2, for example.

I could make obj an instance of a class that has its own save method that takes care of debouncing saves to just that object. Or keep a list of partial/curried functions somewhere, but I'm hoping there's a one stop solution out there. Something like:

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

What I'm looking for the following string of saves to save each object, but only save each object once.

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

EDIT: Something like this, maybe, just not so convoluted, and something that doesn't mutate the obj with a __save function?

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

  obj.__save();
}
like image 840
nicholas Avatar asked Feb 28 '15 21:02

nicholas


People also ask

What is Debouncing in JavaScript with example?

In JavaScript, a debounce function makes sure that your code is only triggered once per user input. Search box suggestions, text-field auto-saves, and eliminating double-button clicks are all use cases for debounce.


1 Answers

You're going to want to create a debounced version of the function for each argument that get's passed. You can do this fairly easily using debounce(), memoize(), and 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

You can see that 'Jim' isn't saved because an object with the same ID is saved right after. The saveDebounced() function is broken down as follows.

The call to _memoize() is caching the debounced function based on some resolver function. In this example, we're simply basing it on the id property. So now we have a way to get the debounced version of save() for any given argument. This is the most important part, because debounce() has all kinds of internal state, and so we need a unique instance of this function for any argument that might get passed to save().

We're using wrap() to invoke the cached function (or creating it then caching it if it's the first call), and pass the function our object. The resulting saveDebounced() function has the exact same signature as save(). The difference is that saveDebounced() will debounce calls based on the argument.


Note: if you want to use this in a more generic way, you can use this helper function:

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)
like image 77
Adam Boduch Avatar answered Sep 23 '22 03:09

Adam Boduch