From ceb821cfd81ca29b0d764b86a03f1e9f1eaa0999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mladen=20Jakovljevi=C4=87?= Date: Thu, 6 Apr 2023 09:11:43 +0200 Subject: [PATCH] fix(throttle): properly handle default ThrottleConfig values (#7176) * fix(throttle): properly handle default ThrottleConfig values * docs(ThrottleConfig): document "leading" and "trailing" behavior (cherry picked from commit b7a4e9493d3242a2b07195c134efa2215a0cbb9f) --- spec/operators/throttle-spec.ts | 6 ++-- spec/operators/throttleTime-spec.ts | 42 +++++++++++------------ src/internal/operators/throttle.ts | 47 ++++++++++++++++++-------- src/internal/operators/throttleTime.ts | 6 ++-- 4 files changed, 59 insertions(+), 42 deletions(-) diff --git a/spec/operators/throttle-spec.ts b/spec/operators/throttle-spec.ts index 70df78f679..8023670bc6 100644 --- a/spec/operators/throttle-spec.ts +++ b/spec/operators/throttle-spec.ts @@ -398,7 +398,7 @@ describe('throttle', () => { ]; const expected = '-a---y----b---x---x---x---|'; - const result = e1.pipe(throttle(() => e2, { leading: true, trailing: true })); + const result = e1.pipe(throttle(() => e2, { trailing: true })); expectObservable(result).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(e1subs); @@ -414,7 +414,7 @@ describe('throttle', () => { const n1Subs = ['--^------------------! ']; const exp = ' --x------------------| '; - const result = s1.pipe(throttle(() => n1, { leading: true, trailing: true })); + const result = s1.pipe(throttle(() => n1, { trailing: true })); expectObservable(result).toBe(exp); expectSubscriptions(s1.subscriptions).toBe(s1Subs); expectSubscriptions(n1.subscriptions).toBe(n1Subs); @@ -433,7 +433,7 @@ describe('throttle', () => { ]; const expected = '-a--------x---(y|)'; - const result = e1.pipe(throttle(() => e2, { leading: true, trailing: true })); + const result = e1.pipe(throttle(() => e2, { trailing: true })); expectObservable(result).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(e1subs); diff --git a/spec/operators/throttleTime-spec.ts b/spec/operators/throttleTime-spec.ts index 81751c40d9..dfc4999088 100644 --- a/spec/operators/throttleTime-spec.ts +++ b/spec/operators/throttleTime-spec.ts @@ -20,7 +20,7 @@ describe('throttleTime operator', () => { const expected = '-a--------b-----c----|'; const subs = ' ^--------------------!'; - const result = e1.pipe(throttleTime(5, rxTest)); + const result = e1.pipe(throttleTime(5)); expectObservable(result).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(subs); @@ -31,7 +31,7 @@ describe('throttleTime operator', () => { of(1, 2, 3) .pipe(throttleTime(5)) .subscribe({ - next: (x: number) => { + next: (x) => { expect(x).to.equal(1); }, complete: done, @@ -41,17 +41,17 @@ describe('throttleTime operator', () => { it('should throttle events multiple times', () => { const expected = ['1-0', '2-0']; concat( - timer(0, 1, rxTest).pipe( + timer(0, 1).pipe( take(3), - map((x: number) => '1-' + x) + map((x) => '1-' + x) ), - timer(8, 1, rxTest).pipe( + timer(8, 1).pipe( take(5), - map((x: number) => '2-' + x) + map((x) => '2-' + x) ) ) .pipe(throttleTime(5, rxTest)) - .subscribe((x: string) => { + .subscribe((x) => { expect(x).to.equal(expected.shift()); }); @@ -64,7 +64,7 @@ describe('throttleTime operator', () => { const subs = ' ^--------------------!'; const expected = '-a--------b-----c----|'; - expectObservable(e1.pipe(throttleTime(5, rxTest))).toBe(expected); + expectObservable(e1.pipe(throttleTime(5))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(subs); }); }); @@ -75,7 +75,7 @@ describe('throttleTime operator', () => { const subs = ' ^------------------------!'; const expected = 'a-----a-----a-----a-----a|'; - expectObservable(e1.pipe(throttleTime(5, rxTest))).toBe(expected); + expectObservable(e1.pipe(throttleTime(5))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(subs); }); }); @@ -86,7 +86,7 @@ describe('throttleTime operator', () => { const subs = ' ^----!'; const expected = '-----|'; - expectObservable(e1.pipe(throttleTime(5, rxTest))).toBe(expected); + expectObservable(e1.pipe(throttleTime(5))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(subs); }); }); @@ -97,7 +97,7 @@ describe('throttleTime operator', () => { const subs = ' ^----!'; const expected = '-----#'; - expectObservable(e1.pipe(throttleTime(10, rxTest))).toBe(expected); + expectObservable(e1.pipe(throttleTime(10))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(subs); }); }); @@ -108,7 +108,7 @@ describe('throttleTime operator', () => { const subs = ' (^!)'; const expected = '|'; - expectObservable(e1.pipe(throttleTime(30, rxTest))).toBe(expected); + expectObservable(e1.pipe(throttleTime(30))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(subs); }); }); @@ -119,7 +119,7 @@ describe('throttleTime operator', () => { const subs = ' ^'; const expected = '-'; - expectObservable(e1.pipe(throttleTime(30, rxTest))).toBe(expected); + expectObservable(e1.pipe(throttleTime(30))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(subs); }); }); @@ -130,7 +130,7 @@ describe('throttleTime operator', () => { const subs = ' (^!)'; const expected = '#'; - expectObservable(e1.pipe(throttleTime(30, rxTest))).toBe(expected); + expectObservable(e1.pipe(throttleTime(30))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(subs); }); }); @@ -142,7 +142,7 @@ describe('throttleTime operator', () => { const subs = ' ^------------------------------!'; const expected = '-a-------------d----------------'; - expectObservable(e1.pipe(throttleTime(5, rxTest)), unsub).toBe(expected); + expectObservable(e1.pipe(throttleTime(5)), unsub).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(subs); }); }); @@ -155,9 +155,9 @@ describe('throttleTime operator', () => { const unsub = ' -------------------------------!'; const result = e1.pipe( - mergeMap((x: string) => of(x)), - throttleTime(5, rxTest), - mergeMap((x: string) => of(x)) + mergeMap((x) => of(x)), + throttleTime(5), + mergeMap((x) => of(x)) ); expectObservable(result, unsub).toBe(expected); @@ -171,7 +171,7 @@ describe('throttleTime operator', () => { const subs = ' ^------------------------------!'; const expected = '-a-------------d---------------#'; - expectObservable(e1.pipe(throttleTime(5, rxTest))).toBe(expected); + expectObservable(e1.pipe(throttleTime(5))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(subs); }); }); @@ -186,7 +186,7 @@ describe('throttleTime operator', () => { // ----|----|---|---| const expected = '-a---y----b---x---x---(x|)'; - const result = e1.pipe(throttleTime(t, rxTest, { leading: true, trailing: true })); + const result = e1.pipe(throttleTime(t, rxTest, { trailing: true })); expectObservable(result).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(e1subs); @@ -199,7 +199,7 @@ describe('throttleTime operator', () => { const t = time(' ----| '); const expected = '-a--------------------|'; - const result = e1.pipe(throttleTime(t, rxTest, { leading: true, trailing: true })); + const result = e1.pipe(throttleTime(t, rxTest, { trailing: true })); expectObservable(result).toBe(expected); }); diff --git a/src/internal/operators/throttle.ts b/src/internal/operators/throttle.ts index 191de58f0e..8c53a1c445 100644 --- a/src/internal/operators/throttle.ts +++ b/src/internal/operators/throttle.ts @@ -5,16 +5,36 @@ import { operate } from '../util/lift'; import { createOperatorSubscriber } from './OperatorSubscriber'; import { innerFrom } from '../observable/innerFrom'; +/** + * An object interface used by {@link throttle} or {@link throttleTime} that ensure + * configuration options of these operators. + * + * @see {@link throttle} + * @see {@link throttleTime} + */ export interface ThrottleConfig { + /** + * If `true`, the resulting Observable will emit the first value from the source + * Observable at the **start** of the "throttling" process (when starting an + * internal timer that prevents other emissions from the source to pass through). + * If `false`, it will not emit the first value from the source Observable at the + * start of the "throttling" process. + * + * If not provided, defaults to: `true`. + */ leading?: boolean; + /** + * If `true`, the resulting Observable will emit the last value from the source + * Observable at the **end** of the "throttling" process (when ending an internal + * timer that prevents other emissions from the source to pass through). + * If `false`, it will not emit the last value from the source Observable at the + * end of the "throttling" process. + * + * If not provided, defaults to: `false`. + */ trailing?: boolean; } -export const defaultThrottleConfig: ThrottleConfig = { - leading: true, - trailing: false, -}; - /** * Emits a value from the source Observable, then ignores subsequent source * values for a duration determined by another Observable, then repeats this @@ -53,20 +73,17 @@ export const defaultThrottleConfig: ThrottleConfig = { * @see {@link sample} * @see {@link throttleTime} * - * @param durationSelector A function - * that receives a value from the source Observable, for computing the silencing - * duration for each source value, returned as an Observable or a Promise. - * @param config a configuration object to define `leading` and `trailing` behavior. Defaults - * to `{ leading: true, trailing: false }`. + * @param durationSelector A function that receives a value from the source + * Observable, for computing the silencing duration for each source value, + * returned as an `ObservableInput`. + * @param config A configuration object to define `leading` and `trailing` + * behavior. Defaults to `{ leading: true, trailing: false }`. * @return A function that returns an Observable that performs the throttle * operation to limit the rate of emissions from the source. */ -export function throttle( - durationSelector: (value: T) => ObservableInput, - config: ThrottleConfig = defaultThrottleConfig -): MonoTypeOperatorFunction { +export function throttle(durationSelector: (value: T) => ObservableInput, config?: ThrottleConfig): MonoTypeOperatorFunction { return operate((source, subscriber) => { - const { leading, trailing } = config; + const { leading = true, trailing = false } = config ?? {}; let hasValue = false; let sendValue: T | null = null; let throttled: Subscription | null = null; diff --git a/src/internal/operators/throttleTime.ts b/src/internal/operators/throttleTime.ts index c0af58203b..de325fe50f 100644 --- a/src/internal/operators/throttleTime.ts +++ b/src/internal/operators/throttleTime.ts @@ -1,5 +1,5 @@ import { asyncScheduler } from '../scheduler/async'; -import { defaultThrottleConfig, throttle } from './throttle'; +import { throttle, ThrottleConfig } from './throttle'; import { MonoTypeOperatorFunction, SchedulerLike } from '../types'; import { timer } from '../observable/timer'; @@ -47,7 +47,7 @@ import { timer } from '../observable/timer'; * internally by the optional `scheduler`. * @param scheduler The {@link SchedulerLike} to use for * managing the timers that handle the throttling. Defaults to {@link asyncScheduler}. - * @param config a configuration object to define `leading` and + * @param config A configuration object to define `leading` and * `trailing` behavior. Defaults to `{ leading: true, trailing: false }`. * @return A function that returns an Observable that performs the throttle * operation to limit the rate of emissions from the source. @@ -55,7 +55,7 @@ import { timer } from '../observable/timer'; export function throttleTime( duration: number, scheduler: SchedulerLike = asyncScheduler, - config = defaultThrottleConfig + config?: ThrottleConfig ): MonoTypeOperatorFunction { const duration$ = timer(duration, scheduler); return throttle(() => duration$, config);