From df0d4395108eb60986b7be1a6b66051e0149e47f Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Tue, 25 Jul 2017 11:10:10 -0700 Subject: [PATCH] feat(debounceTime): add higher-order lettable version of debounceTime --- src/operator/debounceTime.ts | 69 +------------------- src/operators/debounceTime.ts | 119 ++++++++++++++++++++++++++++++++++ src/operators/index.ts | 1 + 3 files changed, 123 insertions(+), 66 deletions(-) create mode 100644 src/operators/debounceTime.ts diff --git a/src/operator/debounceTime.ts b/src/operator/debounceTime.ts index 53fcc732fc..22233cbd94 100644 --- a/src/operator/debounceTime.ts +++ b/src/operator/debounceTime.ts @@ -1,9 +1,8 @@ -import { Operator } from '../Operator'; + import { Observable } from '../Observable'; -import { Subscriber } from '../Subscriber'; import { IScheduler } from '../Scheduler'; -import { Subscription, TeardownLogic } from '../Subscription'; import { async } from '../scheduler/async'; +import { debounceTime as higherOrder } from '../operators'; /** * Emits a value from the source Observable only after a particular time span @@ -52,67 +51,5 @@ import { async } from '../scheduler/async'; * @owner Observable */ export function debounceTime(this: Observable, dueTime: number, scheduler: IScheduler = async): Observable { - return this.lift(new DebounceTimeOperator(dueTime, scheduler)); -} - -class DebounceTimeOperator implements Operator { - constructor(private dueTime: number, private scheduler: IScheduler) { - } - - call(subscriber: Subscriber, source: any): TeardownLogic { - return source.subscribe(new DebounceTimeSubscriber(subscriber, this.dueTime, this.scheduler)); - } -} - -/** - * We need this JSDoc comment for affecting ESDoc. - * @ignore - * @extends {Ignored} - */ -class DebounceTimeSubscriber extends Subscriber { - private debouncedSubscription: Subscription = null; - private lastValue: T = null; - private hasValue: boolean = false; - - constructor(destination: Subscriber, - private dueTime: number, - private scheduler: IScheduler) { - super(destination); - } - - protected _next(value: T) { - this.clearDebounce(); - this.lastValue = value; - this.hasValue = true; - this.add(this.debouncedSubscription = this.scheduler.schedule(dispatchNext, this.dueTime, this)); - } - - protected _complete() { - this.debouncedNext(); - this.destination.complete(); - } - - debouncedNext(): void { - this.clearDebounce(); - - if (this.hasValue) { - this.destination.next(this.lastValue); - this.lastValue = null; - this.hasValue = false; - } - } - - private clearDebounce(): void { - const debouncedSubscription = this.debouncedSubscription; - - if (debouncedSubscription !== null) { - this.remove(debouncedSubscription); - debouncedSubscription.unsubscribe(); - this.debouncedSubscription = null; - } - } -} - -function dispatchNext(subscriber: DebounceTimeSubscriber) { - subscriber.debouncedNext(); + return higherOrder(dueTime, scheduler)(this); } diff --git a/src/operators/debounceTime.ts b/src/operators/debounceTime.ts new file mode 100644 index 0000000000..2c8b8075f5 --- /dev/null +++ b/src/operators/debounceTime.ts @@ -0,0 +1,119 @@ +import { Operator } from '../Operator'; +import { Observable } from '../Observable'; +import { Subscriber } from '../Subscriber'; +import { IScheduler } from '../Scheduler'; +import { Subscription, TeardownLogic } from '../Subscription'; +import { async } from '../scheduler/async'; +import { MonoTypeOperatorFunction } from '../interfaces'; + +/** + * Emits a value from the source Observable only after a particular time span + * has passed without another source emission. + * + * It's like {@link delay}, but passes only the most + * recent value from each burst of emissions. + * + * + * + * `debounceTime` delays values emitted by the source Observable, but drops + * previous pending delayed emissions if a new value arrives on the source + * Observable. This operator keeps track of the most recent value from the + * source Observable, and emits that only when `dueTime` enough time has passed + * without any other value appearing on the source Observable. If a new value + * appears before `dueTime` silence occurs, the previous value will be dropped + * and will not be emitted on the output Observable. + * + * This is a rate-limiting operator, because it is impossible for more than one + * value to be emitted in any time window of duration `dueTime`, but it is also + * a delay-like operator since output emissions do not occur at the same time as + * they did on the source Observable. Optionally takes a {@link IScheduler} for + * managing timers. + * + * @example Emit the most recent click after a burst of clicks + * var clicks = Rx.Observable.fromEvent(document, 'click'); + * var result = clicks.debounceTime(1000); + * result.subscribe(x => console.log(x)); + * + * @see {@link auditTime} + * @see {@link debounce} + * @see {@link delay} + * @see {@link sampleTime} + * @see {@link throttleTime} + * + * @param {number} dueTime The timeout duration in milliseconds (or the time + * unit determined internally by the optional `scheduler`) for the window of + * time required to wait for emission silence before emitting the most recent + * source value. + * @param {Scheduler} [scheduler=async] The {@link IScheduler} to use for + * managing the timers that handle the timeout for each value. + * @return {Observable} An Observable that delays the emissions of the source + * Observable by the specified `dueTime`, and may drop some values if they occur + * too frequently. + * @method debounceTime + * @owner Observable + */ +export function debounceTime(dueTime: number, scheduler: IScheduler = async): MonoTypeOperatorFunction { + return (source: Observable) => source.lift(new DebounceTimeOperator(dueTime, scheduler)); +} + +class DebounceTimeOperator implements Operator { + constructor(private dueTime: number, private scheduler: IScheduler) { + } + + call(subscriber: Subscriber, source: any): TeardownLogic { + return source.subscribe(new DebounceTimeSubscriber(subscriber, this.dueTime, this.scheduler)); + } +} + +/** + * We need this JSDoc comment for affecting ESDoc. + * @ignore + * @extends {Ignored} + */ +class DebounceTimeSubscriber extends Subscriber { + private debouncedSubscription: Subscription = null; + private lastValue: T = null; + private hasValue: boolean = false; + + constructor(destination: Subscriber, + private dueTime: number, + private scheduler: IScheduler) { + super(destination); + } + + protected _next(value: T) { + this.clearDebounce(); + this.lastValue = value; + this.hasValue = true; + this.add(this.debouncedSubscription = this.scheduler.schedule(dispatchNext, this.dueTime, this)); + } + + protected _complete() { + this.debouncedNext(); + this.destination.complete(); + } + + debouncedNext(): void { + this.clearDebounce(); + + if (this.hasValue) { + this.destination.next(this.lastValue); + this.lastValue = null; + this.hasValue = false; + } + } + + private clearDebounce(): void { + const debouncedSubscription = this.debouncedSubscription; + + if (debouncedSubscription !== null) { + this.remove(debouncedSubscription); + debouncedSubscription.unsubscribe(); + this.debouncedSubscription = null; + } + } +} + +function dispatchNext(subscriber: DebounceTimeSubscriber) { + subscriber.debouncedNext(); +} diff --git a/src/operators/index.ts b/src/operators/index.ts index 34dc036b0f..ca7d670383 100644 --- a/src/operators/index.ts +++ b/src/operators/index.ts @@ -12,6 +12,7 @@ export { concatMap } from './concatMap'; export { concatMapTo } from './concatMapTo'; export { count } from './count'; export { debounce } from './debounce'; +export { debounceTime } from './debounceTime'; export { defaultIfEmpty } from './defaultIfEmpty'; export { dematerialize } from './dematerialize'; export { filter } from './filter';