Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix throttle and debounce to be re-entrant #1629

Merged
merged 3 commits into from
May 7, 2014

Conversation

ashanbrown
Copy link

It looks like debounce and throttle are not re-entrant because they clear the function context and args after executing the underlying function, even if the underlying function may call the original function itself. I assume this is done to allow garbage collection. I noticed this because I was triggering a debounced function in an ember application whenever I observed a change in a property that the function might itself change. Turns out ember has it's own throttle and debounce functions but I thought I'd offer up this patch anyway. I believe the fix is to only clear the context and args if a new timer has not been set up.

Andrew

@jdalton
Copy link
Contributor

jdalton commented May 7, 2014

👍, I remember when Underscore removed the unit test for recursive throttle.

counter += this + arg;
if (count > 0) {
count--;
throttledIncr.apply(this, [arg]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be simplified?
The context setting as part of the count seems like unnecessary complexity for the test.

@ashanbrown
Copy link
Author

The context variable provides the value for 'this', which is actually the problem I saw in the first place ('this' being the ember object with a debounced observer). The problem is that context and args are lost, so I think that the test needs to set them.

I did think of that the test might be better if it used string appends rather than counters, but I initially was just following the form of the other tests. With string appends it might look like this:

asyncTest('throttle re-entrant', 2, function() {
    var value = '';
    var throttledAppend;
    var context = 'a';
    var arg = 'b';
    var count = 2;
    var append = function(arg){
      value += this + arg;
      if (count > 0) {
        count--;
        throttledAppend.apply(this, [arg]);
      }
    };
    throttledAppend = _.throttle(append, 32);
    throttledAppend.apply(context, [arg]);
    equal(value, 'ab');
    _.delay(function(){
      equal(value, 'ababab', 'incr was throttled successfully');
      start();
    }, 100);
  });

but I don't think it makes a big difference (same number of lines).

@jdalton
Copy link
Contributor

jdalton commented May 7, 2014

The context variable provides the value for 'this', which is actually the problem I saw in the first place

I'm aware of this but was wondering if it could be simplified. I tackle this in my own implementation and have some tests on it. I'll review tonight and see if it can be refined.

That said the issue is valid and the fix aligns with what I've done.

@ashanbrown
Copy link
Author

Maybe you were just pointing out that the variables were only used once? I just pushed a small simplification you can look for that. I think I originally had put them in variables because I wasn't sure how many times to call them. I ended up using them only once, which seemed sufficient to demonstrate the failure.

@jdalton
Copy link
Contributor

jdalton commented May 7, 2014

Maybe you were just pointing out that the variables were only used once?

It's not that. The way the test is it's hard for me at a glance to see how many times _.throttle or _.debounce is expected to execute without juggling numbers in my head and trying to remember that context fits into that. It'd be great if you tracked contexts in an array of contexts, args in an array of args, and execution count separately then had asserts for each.

@ashanbrown
Copy link
Author

Got it. Just pushed a modification that might make it clearer what the sequence of events is. It's kind of interesting that the expected sequence order needed to be flipped in the throttle re-entrant.

jashkenas added a commit that referenced this pull request May 7, 2014
fix throttle and debounce to be re-entrant
@jashkenas jashkenas merged commit 29a973f into jashkenas:master May 7, 2014
@saifelse
Copy link

@jashkenas could we get a a 1.6.1 release for this? It should be backwards compatible, as no one should expect their debounced functions to be called with the wrong context.

@mponizil
Copy link

Thanks for this fix!

mponizil added a commit to chatid/underscore that referenced this pull request Jun 20, 2014
@akre54 akre54 mentioned this pull request Mar 30, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants