Reputation: 14563
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();
}
Upvotes: 12
Views: 7420
Reputation: 2234
Although I find Adam Boduch's answer relevant, I find the method with lodash very unintelligible. I personally use a "vanilla" version:
const debounce = (fn, delay = 1000) => {
let timerId = {};
return function(...args) {
const context = this;
const key = JSON.stringify(args);
clearTimeout(timerId[key]);
timerId[key] = setTimeout(() => {
delete timerId[key];
fn.apply(context, args);
}, delay);
};
}
// Example of use
const debouncedFunction = debounce((arg) => {
console.log('Function called with', arg);
}, 2000);
debouncedFunction('param1'); // Will run after 2 seconds
debouncedFunction('param1'); // Cancels the first call, reschedules another call for 2 seconds later
debouncedFunction('param2'); // Will run in parallel with the second call, after 2 seconds
This approach also has the advantage of not depending on the format of the function parameter. Here, the function to be debounced can receive a single parameter (which is not specifically an object), but can also contain several of different types.
Upvotes: 0
Reputation: 11211
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)
Upvotes: 30
Reputation: 761
You can use internal object in closure for set/get debounced function.
In this example we check if debounced function with this args alredy saved in our memory
object while call debounced function. If no - we create it.
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);
};
};
Original gist - https://gist.github.com/nzvtrk/1a444cdf6a86a5a6e6d6a34f0db19065
Upvotes: 1
Reputation: 3412
Maybe something like:
var previouslySeen = {}
var save = _.debounce(function(obj) {
var key = JSON.stringify(obj);
if (!previouslySeen[key]) {
previouslySeen[key] = 1;
} else {
...
}
}, delay);
Upvotes: 0