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

Eager throttle #1396

Closed
olegam opened this issue Jul 1, 2014 · 7 comments
Closed

Eager throttle #1396

olegam opened this issue Jul 1, 2014 · 7 comments
Assignees
Milestone

Comments

@olegam
Copy link

olegam commented Jul 1, 2014

I need to operate on a signal with the following behavior:

  1. If a next is received and it did not send a next for the last interval seconds it should be forwarded immediately.
  2. If a next is received and another next was just sent it will be queued and sent interval seconds after the previous one unless a new next is received before that.
  3. If another next is received while one is waiting the first one is discarded and the latest will be sent interval seconds after the last next was sent.

The signature could be like:

- (RACSignal *)eagerThrottle:(NSTimeInterval)interval;

Anyone who has done something like this? I have a lot of use cases where I prefer this behavior over throttle:.

@epatey
Copy link
Contributor

epatey commented Jul 2, 2014

I have written throttleForInterval:afterAllowing: for my own purposes. I think it's pretty close. This lets me strike the balance between having a very responsive UI in most cases - i.e. no lag for the throttle timeout to expire - and not hammering my UI if many changes come in at once.

- (RACSignal *)throttleForInterval:(NSTimeInterval)interval
                     afterAllowing:(uint)count {
    __block NSTimeInterval timeFirstEventPassedThrough;
    __block NSTimeInterval timeFirstEventThrottled = 0;
    __block uint numberEventsPassedThrough = 0;
    return [self throttle:interval
        valuesPassingTest:^BOOL(id next) {
            NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
            if (numberEventsPassedThrough == 0) {
                timeFirstEventPassedThrough = now;
                numberEventsPassedThrough = 1;
                return NO;
            }

            if (timeFirstEventThrottled) {
                if (now > (timeFirstEventThrottled + interval)) {
                    // First event after most recent interval. No throttle
                    timeFirstEventPassedThrough = now;
                    timeFirstEventThrottled = 0;
                    numberEventsPassedThrough = 1;
                    return NO;
                }
                else {
                    // Still inside current throttle interval. Keep throttling
                    return YES;
                }
            }
            else if (timeFirstEventPassedThrough) {
                if (now > (timeFirstEventPassedThrough + interval)) {
                    // First event after most recent interval. No throttle
                    timeFirstEventPassedThrough = now;
                    timeFirstEventThrottled = 0;
                    numberEventsPassedThrough = 1;
                    return NO;
                }

                if (numberEventsPassedThrough < count) {
                    numberEventsPassedThrough++;
                    return NO;
                }
            }

            timeFirstEventThrottled = now;
            return YES;
        }];
}

@olegam
Copy link
Author

olegam commented Jul 2, 2014

Thanks for sharing. But that is an lot of code and state. I was hoping for something a little more elegant based on composing existing signal methods.

@epatey
Copy link
Contributor

epatey commented Jul 2, 2014

Fair enough. If you can do it without any state, I'll throw this code out in favor of yours. 😉

The way I think about reactive stuff is that you move the state from one form (manual and imperative) to another (declarative and hidden). The state's still there inside the signal compositions and subscriptions; it's just not something the client code has to worry about.

In case it wasn't clear, my code is a RACSignal category method like all of the others, so the state is encapsulated within it. The client code that uses it doesn't have to be aware of the stateful implementation details of the signal.

@jspahrsummers
Copy link
Member

If I could go back in time, this is how I would want -throttle: itself to behave. Since that ship has sailed, though, I'd love to see something like this become the default in #1382. 😄

@jspahrsummers jspahrsummers added this to the 3.0 milestone Jul 11, 2014
@jspahrsummers jspahrsummers self-assigned this Jul 11, 2014
@olegam
Copy link
Author

olegam commented Jul 11, 2014

@epatey Your solution works really well for me actually. I guess you are right it is not easy to implement this just by composing existing signal operators.

jspahrsummers added a commit that referenced this issue Jul 11, 2014
Implements the behavior proposed in #1396.
@epatey
Copy link
Contributor

epatey commented Jul 11, 2014

@olegam, thanks. Glad it was useful.

@jspahrsummers
Copy link
Member

This was addressed in c8807fb. Don't know why I left this open. ⚡

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants