Skip to content

Commit

Permalink
fix(throttle): properly handle default ThrottleConfig values (#7176)
Browse files Browse the repository at this point in the history
* fix(throttle): properly handle default ThrottleConfig values

* docs(ThrottleConfig): document "leading" and "trailing" behavior

(cherry picked from commit b7a4e94)
  • Loading branch information
jakovljevic-mladen committed Apr 6, 2023
1 parent 6adc66f commit ceb821c
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 42 deletions.
6 changes: 3 additions & 3 deletions spec/operators/throttle-spec.ts
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
42 changes: 21 additions & 21 deletions spec/operators/throttleTime-spec.ts
Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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());
});

Expand All @@ -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);
});
});
Expand All @@ -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);
});
});
Expand All @@ -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);
});
});
Expand All @@ -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);
});
});
Expand All @@ -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);
});
});
Expand All @@ -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);
});
});
Expand All @@ -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);
});
});
Expand All @@ -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);
});
});
Expand All @@ -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);
Expand All @@ -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);
});
});
Expand All @@ -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);
Expand All @@ -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);
});
Expand Down
47 changes: 32 additions & 15 deletions src/internal/operators/throttle.ts
Expand Up @@ -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
Expand Down Expand Up @@ -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<T>(
durationSelector: (value: T) => ObservableInput<any>,
config: ThrottleConfig = defaultThrottleConfig
): MonoTypeOperatorFunction<T> {
export function throttle<T>(durationSelector: (value: T) => ObservableInput<any>, config?: ThrottleConfig): MonoTypeOperatorFunction<T> {
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;
Expand Down
6 changes: 3 additions & 3 deletions 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';

Expand Down Expand Up @@ -47,15 +47,15 @@ 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.
*/
export function throttleTime<T>(
duration: number,
scheduler: SchedulerLike = asyncScheduler,
config = defaultThrottleConfig
config?: ThrottleConfig
): MonoTypeOperatorFunction<T> {
const duration$ = timer(duration, scheduler);
return throttle(() => duration$, config);
Expand Down

0 comments on commit ceb821c

Please sign in to comment.