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(throttleTime): leading and trailing true should only emit a single value per time frame #2749

Closed
wants to merge 5 commits into from

Conversation

sod
Copy link

@sod sod commented Jul 14, 2017

Fixes #2727

fix 1: throttleTime {leading: true, trailing: true}

Currently it behaves like

'a1234b1234c1234d12e--|'
'a----b1----12----2e--|'

By not scheduling a new throttle when emitting a trailing value.

this PR changes it to:

'a1234b1234c1234d12e--|'
'a----b----c----d----e|'

fix 2: throttleTime {leading: false, trailing: true}

Currently it behaves like

'a-------------a-|'
'----------------|'

First and only value in each timeframe is dropped by not storing the first value when not yet throttled. So this code doesn't console.log anything:

Rx.Observable.of(1)
    .throttleTime(100, undefined, {leading: false, trailing: true})
    .subscribe((value) => console.log(value))

This PR changes the behavior to:

'a-------------a-|'
'-----a-------------a|'

I added tests to cover those issues.

@rxjs-bot
Copy link

rxjs-bot commented Jul 14, 2017

Messages
📖

CJS: 1342.7KB, global: 737.4KB (gzipped: 137.9KB), min: 144.7KB (gzipped: 30.7KB)

Generated by 🚫 dangerJS

@coveralls
Copy link

coveralls commented Jul 14, 2017

Coverage Status

Coverage remained the same at ?% when pulling 1b6c92a on sod:throttle-time-single-emit into d24f5b9 on ReactiveX:master.

@sod
Copy link
Author

sod commented Aug 1, 2017

@benlesh it's a followup to #2465 ... what do you think? :)

@Parakleta
Copy link

I've been testing this, and the only hiccup is that the throttle call should come before the .next() emission because otherwise if the .next() is resolved synchronously it can really mess up the timing.

I suggest changing _next() to have something like:

let emit = false;
if (!this.throttled && this.leading) {
    emit = true;
} else ...

if (!this.throttled) this.throttle();
if (emit) this.destination.next(value);

And also clearThrottle to something like:

if (this.trailing && this._hasTrailingValue) {
    let value = this._trailingValue;
    this._trailingValue = null;
    this._hasTrailingValue = false;
    this.throttle();
    this.destination.next(value);
}

I have also recommended that this PR be replicated to solve #2466 but considering what I have discovered regarding the timing this may not be so simple.

@sod
Copy link
Author

sod commented Sep 26, 2017

How did you test the hiccup? next is not being called when a throttle is present, so I can't see how that happens.

if (!this.throttled && this.leading) {
    this.destination.next(value);
} /* ...

@Parakleta
Copy link

Later in the same function is the call to this.throttle(). If next() takes 20ms to complete and our throttle is supposed to be 100ms then it will actually be 120ms between emissions because the setting of throttle doesn't start until after next completes.

@sod
Copy link
Author

sod commented Sep 26, 2017

Thanks, now I understood the issue.

@sod
Copy link
Author

sod commented Sep 26, 2017

rebased & added your feedback

@sod sod mentioned this pull request Sep 26, 2017
@sod sod force-pushed the throttle-time-single-emit branch 2 times, most recently from bc1b6cc to e0953f1 Compare September 26, 2017 11:56
}

if (!this.throttled) {
this.throttle();
Copy link
Member

Choose a reason for hiding this comment

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

Can we refactor this a little to be:

if (!this.trottled) {
  this.throttle();
  if (this.leading) {
    this.destination.next(value);
  }
}

if (this.trailing) {
  this._trailingValue = value;
  this._hasTrailingValue = true;
}

I'm not sure in it's current configuration it will work as desired, because of that else if. If it's trailing, we always want to record the value, not only if we've already started throttling.

Copy link
Author

Choose a reason for hiding this comment

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

Then throttleTime would emit the same input twice on {leading:true, trailing: true}. I guess in the end it doesn't matter when throttleTime is used in conjunction with distinctUntilChanged, but it feels wrong regardless.

Observable.of(1).throttleTime(100, {leading:true, trailing: true}).subscribe((n) => console.log(n));

// 1
// 1

vs. lodash

fn = _.throttle((n) => console.log(n), 100, {leading:true, trailing: true});
fn(1);

// 1

this._trailingValue = null;
this._hasTrailingValue = false;
this.throttle();
Copy link
Member

Choose a reason for hiding this comment

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

this is unnecessary and changes the behavior of the operator. It's not supposed to start throttling again when the throttle is cleared.

Copy link
Author

Choose a reason for hiding this comment

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

But this is exactly why I opened the PR. Because currently the operator emits 2 values each timeframe instead of 1.

If the doubleemit is intended, this PR can be closed and #2727 & #2859 rejected.

Copy link
Member

Choose a reason for hiding this comment

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

hmm.. I think there's a disconnect here. throttle() actually starts the throttle measurement. The, with leading and trailing both true, it should emit the same as it would with leading behavior, but then emit the trailing value at the end.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I okay, I actually see what you're saying now. This is to start the throttle again as soon as there's another emission (due to trailing behavior)

Copy link
Member

Choose a reason for hiding this comment

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

Okay... the more I look at this, the more I think it will have to be a change that lands in v 6.0, because it will be a breaking change for someone. (Albeit a fix)

if (throttled) {
throttled.unsubscribe();
this.remove(throttled);
Copy link
Member

Choose a reason for hiding this comment

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

this is unnecessary, the unsubscribe() will remove the inner subscription.


expectObservable(e1.throttleTime(50, rxTestScheduler)).toBe(expected);
expectObservable(e1.throttleTime(50, rxTestScheduler, {leading: true, trailing: false})).toBe(expected);
Copy link
Member

Choose a reason for hiding this comment

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

if you could use the time function here and represent the time as marbles above like:

const t1 = '----|';

and then use time(t1) which returns a number.

That would be ideal as the test could be more readable, you can even used commented out ----| marblse to demonstrate where the throttles occur.

Remember, the whole purpose of these tests is to be more readable.

Copy link
Member

Choose a reason for hiding this comment

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

Please do the same for the other tests.

@benlesh
Copy link
Member

benlesh commented Sep 26, 2017

@sod can you look at what I've done with throttle in #2864 and try to mirror it as closely as possible to keep the code base consistent?

@lock
Copy link

lock bot commented Jun 6, 2018

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Jun 6, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

throttleTime(n, undefined, {leading: true, trailing: true}) ... double output
5 participants