lodash debounce not working in anonymous function
Asked Answered
F

6

98

Hello I cannot seem to figure out why the debounce function works as expected when passed directly to a keyup event; but it does not work if I wrap it inside an anonymous function.

I have fiddle of the problem: http://jsfiddle.net/6hg95/1/

EDIT: Added all the things I tried.

HTML

<input id='anonFunction'/>
<input id='noReturnAnonFunction'/>
<input id='exeDebouncedFunc'/>
<input id='function'/>
<div id='output'></div>

JAVASCRIPT

$(document).ready(function(){
    $('#anonFunction').on('keyup', function () {
        return _.debounce(debounceIt, 500, false); //Why does this differ from #function
    });
    $('#noReturnAnonFunction').on('keyup', function () {
        _.debounce(debounceIt, 500, false); //Not being executed
    });
    $('#exeDebouncedFunc').on('keyup', function () {
        _.debounce(debounceIt, 500, false)(); //Executing the debounced function results in wrong behaviour
    });
    $('#function').on('keyup', _.debounce(debounceIt, 500, false)); //This is working.
});

function debounceIt(){
    $('#output').append('debounced');
}

anonFunction and noReturnAnonFunction does not fire the debounce function; but the last function does fire. I do not understand why this is. Can anybody please help me understand this?

EDIT Ok, so the reason that the debounce does not happen in #exeDebouncedFunc (the one you refer) is because the function is executed in the scope of the anonymous function and another keyup event will create a new function in another anonymous scope; thus firing the debounced function as many times as you type something (instead of firing once which would be the expected behaviour; see beviour of #function)?

Can you please explain the difference between #anonFunction and the #function. Is this again a matter of scoping why one of them works and the other does not?

EDIT Ok, so now I understand why this is happening. And here is why I needed to wrap it inside an anonymous function:

Fiddle: http://jsfiddle.net/6hg95/5/

HTML

<input id='anonFunction'/>
<div id='output'></div>

JAVASCRIPT

(function(){
    var debounce = _.debounce(fireServerEvent, 500, false);

    $('#anonFunction').on('keyup', function () {
        //clear textfield
        $('#output').append('clearNotifications<br/>');
        debounce();
    });

    function fireServerEvent(){
        $('#output').append('serverEvent<br/>');
    }
})();
Foreplay answered 19/6, 2014 at 12:10 Comment(1)
The title of the question assuming, it is right helped me.Equipage
S
95

As Palpatim explained, the reason lies in the fact that _.debounce(...) returns a function, which when invoked does its magic.

Therefore in your #anonFunction example, you have a key listener, which when invoked does nothing but return a function to the invoker, which does nothing with the return values from the event listener.

This is a snippet of the _.debounce(...) definition:

_.debounce
function (func, wait, immediate) {
    var timeout;
    return function() {
      var context = this, args = arguments;
      var later = function() {
        timeout = null;
        if (!immediate) func.apply(context, args);
      };
      if (immediate && !timeout) func.apply(context, args);
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  } 

Your key event listener must invoke the returned function from _.debounce(...), or you can do as in your non-anonymous example and use the returned function from the _.debounce(...) call as your event listener.

Superfine answered 20/6, 2014 at 7:20 Comment(1)
+1 for this explanation. Basically this: $('#function').on('keyup', _.debounce(debounceIt, 500, false)); //This is working. is WHY debounce returns a function. It's beautiful like this, and Just Makes Sense™Chaiken
C
88

Think easier

_.debounce returns a debounced function! So instead of thinking in terms of

$el.on('keyup'), function(){
   _.debounce(doYourThing,500); //uh I want to debounce this
}

you rather call the debounced function instead

var doYourThingDebounced = _.debounce(doYourThing, 500); //YES, this will always be debounced

$el.on('keyup', doYourThingDebounced);
Countercheck answered 25/2, 2017 at 12:8 Comment(3)
What if I have to call multiple functions on keyup and only one of them should be debounced?Tenerife
@Tenerife - doYourThingDebounced is a function; you can just call it as part of your event handler: $el.on('keyup', function() { doYourThingDebounced(); doYourOtherThing(); }Paynim
I think you can even just do $el.on('keyup', _.debounce(doYourThing,500)).Karalee
G
37

debounce doesn't execute the function, it returns a function with the debounciness built into it.

Returns

(Function): Returns the new debounced function.

So your #function handler is actually doing the Right Thing, by returning a function to be used by jQuery as a keyup handler. To fix your #noReturnAnonFunction example, you could simply execute the debounced function in the context of your function:

$('#noReturnAnonFunction').on('keyup', function () {
    _.debounce(debounceIt, 500, false)(); // Immediately executes
});

But that's introducing a needless anonymous function wrapper around your debounce.

Ginder answered 19/6, 2014 at 15:8 Comment(2)
So many upvotes, but imho this can not work. On every keyup the function debounceIt will be converted into a debounced function AND executed immediately, with the effect that debounceIt will be delayed for 500ms. This will be repeated for every keypress! So in the end, your code is equivalent to $('#noReturnAnonFunction').on('keyup', function (){setTimeout(debounceIt, 500);}) No debouncing... only delaying.Countercheck
you are right. I don't think it's debouncing. It's delayingRainbolt
T
7

You can return the debounce function like this:

(function(){
    var debounce = _.debounce(fireServerEvent, 500, false);

    $('#anonFunction').on('keyup', function () {
        //clear textfield
        $('#output').append('clearNotifications<br/>');
        return debounce();
    });

    function fireServerEvent(){
        $('#output').append('serverEvent<br/>');
    }
})();
Tisatisane answered 27/1, 2017 at 10:32 Comment(3)
It seems this is not needed in this situation. Similar idea helped me in another situation though.Tisatisane
_.debounce does not return the function, but the time it is set to timeout for.Characteristically
According to documentation _.debounce does return the function. ref. lodash.com/docs/#debounceHandtohand
C
5

Came across this while looking for a solution to calling a debounce with a trailing call, found this article which really helped me: https://newbedev.com/lodash-debounce-not-working-in-react specifically:

Solution for those who came here because throttle / debounce doesn't work >with FunctionComponent - you need to store debounced function via useRef():

export const ComponentName = (value = null) => {
  const [inputValue, setInputValue] = useState(value);

  const setServicesValue = value => Services.setValue(value);

  const setServicesValueDebounced = useRef(_.debounce(setServicesValue, 1000));

  const handleChange = ({ currentTarget: { value } }) => {
    setInputValue(value);
    setServicesValueDebounced.current(value);
  };

  return <input onChange={handleChange} value={inputValue} />;
};
Crosslegged answered 29/7, 2021 at 11:29 Comment(1)
What is Services.setValue(value);Buell
P
0

More generally, if you want a debounce with a trailing behaviour (accounts for last click, or more likely last change on a select input), and a visual feedback on first click/change, you are faced with the same issue.

This does not work:

$(document).on('change', "#select", function() {
    $('.ajax-loader').show();
    _.debounce(processSelectChange, 1000);
});

This would be a solution:

$(document).on('change', "#select", function() {
    $('.ajax-loader').show();
});
$(document).on('change', "#select", _.debounce(processSelectChange, 1000));
Preterhuman answered 19/1, 2017 at 14:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.