Skip to content

Commit 22eb9a6

Browse files
Use 'debounce' as proper name for ThrottleWithTimeout which unfortunately is the poorly named Rx Throttle operator.
http://drupalmotion.com/article/debounce-and-throttle-visual-explanation Debounce: Think of it as "grouping multiple events in one". Imagine that you go home, enter in the elevator, doors are closing... and suddenly your neighbor appears in the hall and tries to jump on the elevator. Be polite! and open the doors for him: you are debouncing the elevator departure. Consider that the same situation can happen again with a third person, and so on... probably delaying the departure several minutes. Throttle: Think of it as a valve, it regulates the flow of the executions. We can determine the maximum number of times a function can be called in certain time. So in the elevator analogy.. you are polite enough to let people in for 10 secs, but once that delay passes, you must go! http://unscriptable.com/2009/03/20/debouncing-javascript-methods/ http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/
1 parent accdd96 commit 22eb9a6

File tree

2 files changed

+112
-20
lines changed

2 files changed

+112
-20
lines changed

rxjava-core/src/main/java/rx/Observable.java

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
import rx.operators.OperationTakeUntil;
6565
import rx.operators.OperationTakeWhile;
6666
import rx.operators.OperationThrottleFirst;
67-
import rx.operators.OperationThrottleWithTimeout;
67+
import rx.operators.OperationDebounce;
6868
import rx.operators.OperationTimestamp;
6969
import rx.operators.OperationToObservableFuture;
7070
import rx.operators.OperationToObservableIterable;
@@ -1811,23 +1811,83 @@ public static Observable<Long> interval(long interval, TimeUnit unit, Scheduler
18111811
}
18121812

18131813
/**
1814-
* Throttles by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call.
1814+
* Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call.
18151815
* <p>
18161816
* NOTE: If events keep firing faster than the timeout then no data will be emitted.
1817+
* <p>
1818+
* Information on debounce vs throttle:
1819+
* <p>
1820+
* <ul>
1821+
* <li>http://drupalmotion.com/article/debounce-and-throttle-visual-explanation</li>
1822+
* <li>http://unscriptable.com/2009/03/20/debouncing-javascript-methods/</li>
1823+
* <li>http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/</li>
1824+
* </ul>
18171825
*
18181826
* @param timeout
18191827
* The time each value has to be 'the most recent' of the {@link Observable} to ensure that it's not dropped.
18201828
* @param unit
18211829
* The {@link TimeUnit} for the timeout.
18221830
*
18231831
* @return An {@link Observable} which filters out values which are too quickly followed up with newer values.
1832+
* @see {@link #throttleWithTimeout};
1833+
*/
1834+
public Observable<T> debounce(long timeout, TimeUnit unit) {
1835+
return create(OperationDebounce.debounce(this, timeout, unit));
1836+
}
1837+
1838+
/**
1839+
* Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call.
1840+
* <p>
1841+
* NOTE: If events keep firing faster than the timeout then no data will be emitted.
1842+
* <p>
1843+
* Information on debounce vs throttle:
1844+
* <p>
1845+
* <ul>
1846+
* <li>http://drupalmotion.com/article/debounce-and-throttle-visual-explanation</li>
1847+
* <li>http://unscriptable.com/2009/03/20/debouncing-javascript-methods/</li>
1848+
* <li>http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/</li>
1849+
* </ul>
1850+
*
1851+
* @param timeout
1852+
* The time each value has to be 'the most recent' of the {@link Observable} to ensure that it's not dropped.
1853+
* @param unit
1854+
* The unit of time for the specified timeout.
1855+
* @param scheduler
1856+
* The {@link Scheduler} to use internally to manage the timers which handle timeout for each event.
1857+
* @return Observable which performs the throttle operation.
1858+
* @see {@link #throttleWithTimeout};
1859+
*/
1860+
public Observable<T> debounce(long timeout, TimeUnit unit, Scheduler scheduler) {
1861+
return create(OperationDebounce.debounce(this, timeout, unit));
1862+
}
1863+
1864+
/**
1865+
* Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call.
1866+
* <p>
1867+
* NOTE: If events keep firing faster than the timeout then no data will be emitted.
1868+
* <p>
1869+
* Information on debounce vs throttle:
1870+
* <p>
1871+
* <ul>
1872+
* <li>http://drupalmotion.com/article/debounce-and-throttle-visual-explanation</li>
1873+
* <li>http://unscriptable.com/2009/03/20/debouncing-javascript-methods/</li>
1874+
* <li>http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/</li>
1875+
* </ul>
1876+
*
1877+
* @param timeout
1878+
* The time each value has to be 'the most recent' of the {@link Observable} to ensure that it's not dropped.
1879+
* @param unit
1880+
* The {@link TimeUnit} for the timeout.
1881+
*
1882+
* @return An {@link Observable} which filters out values which are too quickly followed up with newer values.
1883+
* @see {@link #debounce}
18241884
*/
18251885
public Observable<T> throttleWithTimeout(long timeout, TimeUnit unit) {
1826-
return create(OperationThrottleWithTimeout.throttleWithTimeout(this, timeout, unit));
1886+
return create(OperationDebounce.debounce(this, timeout, unit));
18271887
}
18281888

18291889
/**
1830-
* Throttles by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call.
1890+
* Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call.
18311891
* <p>
18321892
* NOTE: If events keep firing faster than the timeout then no data will be emitted.
18331893
*
@@ -1838,11 +1898,12 @@ public Observable<T> throttleWithTimeout(long timeout, TimeUnit unit) {
18381898
* @param scheduler
18391899
* The {@link Scheduler} to use internally to manage the timers which handle timeout for each event.
18401900
* @return Observable which performs the throttle operation.
1901+
* @see {@link #debounce}
18411902
*/
18421903
public Observable<T> throttleWithTimeout(long timeout, TimeUnit unit, Scheduler scheduler) {
1843-
return create(OperationThrottleWithTimeout.throttleWithTimeout(this, timeout, unit, scheduler));
1904+
return create(OperationDebounce.debounce(this, timeout, unit, scheduler));
18441905
}
1845-
1906+
18461907
/**
18471908
* Throttles by skipping value until `skipDuration` passes and then emits the next received value.
18481909
* <p>

rxjava-core/src/main/java/rx/operators/OperationThrottleWithTimeout.java renamed to rxjava-core/src/main/java/rx/operators/OperationDebounce.java

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
* quickly followed up with other values. Values which are not followed up by other values within the specified timeout are published
4242
* as soon as the timeout expires.
4343
*/
44-
public final class OperationThrottleWithTimeout {
44+
public final class OperationDebounce {
4545

4646
/**
4747
* This operation filters out events which are published too quickly in succession. This is done by dropping events which are
@@ -56,8 +56,8 @@ public final class OperationThrottleWithTimeout {
5656
* The unit of time for the specified timeout.
5757
* @return A {@link Func1} which performs the throttle operation.
5858
*/
59-
public static <T> OnSubscribeFunc<T> throttleWithTimeout(Observable<T> items, long timeout, TimeUnit unit) {
60-
return throttleWithTimeout(items, timeout, unit, Schedulers.threadPoolForComputation());
59+
public static <T> OnSubscribeFunc<T> debounce(Observable<T> items, long timeout, TimeUnit unit) {
60+
return debounce(items, timeout, unit, Schedulers.threadPoolForComputation());
6161
}
6262

6363
/**
@@ -75,23 +75,23 @@ public static <T> OnSubscribeFunc<T> throttleWithTimeout(Observable<T> items, lo
7575
* The {@link Scheduler} to use internally to manage the timers which handle timeout for each event.
7676
* @return A {@link Func1} which performs the throttle operation.
7777
*/
78-
public static <T> OnSubscribeFunc<T> throttleWithTimeout(final Observable<T> items, final long timeout, final TimeUnit unit, final Scheduler scheduler) {
78+
public static <T> OnSubscribeFunc<T> debounce(final Observable<T> items, final long timeout, final TimeUnit unit, final Scheduler scheduler) {
7979
return new OnSubscribeFunc<T>() {
8080
@Override
8181
public Subscription onSubscribe(Observer<? super T> observer) {
82-
return new Throttle<T>(items, timeout, unit, scheduler).onSubscribe(observer);
82+
return new Debounce<T>(items, timeout, unit, scheduler).onSubscribe(observer);
8383
}
8484
};
8585
}
8686

87-
private static class Throttle<T> implements OnSubscribeFunc<T> {
87+
private static class Debounce<T> implements OnSubscribeFunc<T> {
8888

8989
private final Observable<T> items;
9090
private final long timeout;
9191
private final TimeUnit unit;
9292
private final Scheduler scheduler;
9393

94-
public Throttle(Observable<T> items, long timeout, TimeUnit unit, Scheduler scheduler) {
94+
public Debounce(Observable<T> items, long timeout, TimeUnit unit, Scheduler scheduler) {
9595
this.items = items;
9696
this.timeout = timeout;
9797
this.unit = unit;
@@ -100,11 +100,11 @@ public Throttle(Observable<T> items, long timeout, TimeUnit unit, Scheduler sche
100100

101101
@Override
102102
public Subscription onSubscribe(Observer<? super T> observer) {
103-
return items.subscribe(new ThrottledObserver<T>(observer, timeout, unit, scheduler));
103+
return items.subscribe(new DebounceObserver<T>(observer, timeout, unit, scheduler));
104104
}
105105
}
106106

107-
private static class ThrottledObserver<T> implements Observer<T> {
107+
private static class DebounceObserver<T> implements Observer<T> {
108108

109109
private final Observer<? super T> observer;
110110
private final long timeout;
@@ -113,7 +113,7 @@ private static class ThrottledObserver<T> implements Observer<T> {
113113

114114
private final AtomicReference<Subscription> lastScheduledNotification = new AtomicReference<Subscription>();
115115

116-
public ThrottledObserver(Observer<? super T> observer, long timeout, TimeUnit unit, Scheduler scheduler) {
116+
public DebounceObserver(Observer<? super T> observer, long timeout, TimeUnit unit, Scheduler scheduler) {
117117
// we need to synchronize the observer since the on* events can be coming from different
118118
// threads and are thus non-deterministic and could be interleaved
119119
this.observer = new SynchronizedObserver<T>(observer);
@@ -174,7 +174,7 @@ public void before() {
174174
}
175175

176176
@Test
177-
public void testThrottlingWithCompleted() {
177+
public void testDebounceWithCompleted() {
178178
Observable<String> source = Observable.create(new OnSubscribeFunc<String>() {
179179
@Override
180180
public Subscription onSubscribe(Observer<? super String> observer) {
@@ -187,7 +187,7 @@ public Subscription onSubscribe(Observer<? super String> observer) {
187187
}
188188
});
189189

190-
Observable<String> sampled = Observable.create(OperationThrottleWithTimeout.throttleWithTimeout(source, 400, TimeUnit.MILLISECONDS, scheduler));
190+
Observable<String> sampled = Observable.create(OperationDebounce.debounce(source, 400, TimeUnit.MILLISECONDS, scheduler));
191191
sampled.subscribe(observer);
192192

193193
scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS);
@@ -201,7 +201,38 @@ public Subscription onSubscribe(Observer<? super String> observer) {
201201
}
202202

203203
@Test
204-
public void testThrottlingWithError() {
204+
public void testDebounceNeverEmits() {
205+
Observable<String> source = Observable.create(new OnSubscribeFunc<String>() {
206+
@Override
207+
public Subscription onSubscribe(Observer<? super String> observer) {
208+
// all should be skipped since they are happening faster than the 200ms timeout
209+
publishNext(observer, 100, "a"); // Should be skipped
210+
publishNext(observer, 200, "b"); // Should be skipped
211+
publishNext(observer, 300, "c"); // Should be skipped
212+
publishNext(observer, 400, "d"); // Should be skipped
213+
publishNext(observer, 500, "e"); // Should be skipped
214+
publishNext(observer, 600, "f"); // Should be skipped
215+
publishNext(observer, 700, "g"); // Should be skipped
216+
publishNext(observer, 800, "h"); // Should be skipped
217+
publishCompleted(observer, 900); // Should be published as soon as the timeout expires.
218+
219+
return Subscriptions.empty();
220+
}
221+
});
222+
223+
Observable<String> sampled = Observable.create(OperationDebounce.debounce(source, 200, TimeUnit.MILLISECONDS, scheduler));
224+
sampled.subscribe(observer);
225+
226+
scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS);
227+
InOrder inOrder = inOrder(observer);
228+
inOrder.verify(observer, times(0)).onNext(anyString());
229+
scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS);
230+
inOrder.verify(observer, times(1)).onCompleted();
231+
inOrder.verifyNoMoreInteractions();
232+
}
233+
234+
@Test
235+
public void testDebounceWithError() {
205236
Observable<String> source = Observable.create(new OnSubscribeFunc<String>() {
206237
@Override
207238
public Subscription onSubscribe(Observer<? super String> observer) {
@@ -214,7 +245,7 @@ public Subscription onSubscribe(Observer<? super String> observer) {
214245
}
215246
});
216247

217-
Observable<String> sampled = Observable.create(OperationThrottleWithTimeout.throttleWithTimeout(source, 400, TimeUnit.MILLISECONDS, scheduler));
248+
Observable<String> sampled = Observable.create(OperationDebounce.debounce(source, 400, TimeUnit.MILLISECONDS, scheduler));
218249
sampled.subscribe(observer);
219250

220251
scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS);

0 commit comments

Comments
 (0)