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(debounceTime): improves performance on quick succession of emits #6049

Merged
merged 1 commit into from
Feb 24, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 60 additions & 5 deletions src/internal/operators/debounceTime.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { asyncScheduler } from '../scheduler/async';
import { MonoTypeOperatorFunction, SchedulerLike } from '../types';
import { debounce } from './debounce';
import { timer } from '../observable/timer';
import { Subscription } from '../Subscription';
import { MonoTypeOperatorFunction, SchedulerAction, SchedulerLike } from '../types';
import { operate } from '../util/lift';
import { OperatorSubscriber } from './OperatorSubscriber';

/**
* Emits a notification from the source Observable only after a particular time span
Expand Down Expand Up @@ -61,6 +62,60 @@ import { timer } from '../observable/timer';
* too frequently.
*/
export function debounceTime<T>(dueTime: number, scheduler: SchedulerLike = asyncScheduler): MonoTypeOperatorFunction<T> {
const duration = timer(dueTime, scheduler);
return debounce(() => duration);
return operate((source, subscriber) => {
let activeTask: Subscription | null = null;
let lastValue: T | null = null;
let lastTime: number | null = null;

const emit = () => {
if (activeTask) {
// We have a value! Free up memory first, then emit the value.
activeTask.unsubscribe();
activeTask = null;
const value = lastValue!;
lastValue = null;
subscriber.next(value);
}
};
function emitWhenIdle(this: SchedulerAction<unknown>) {
// This is called `dueTime` after the first value
// but we might have received new values during this window!

const targetTime = lastTime! + dueTime;
const now = scheduler.now();
if (now < targetTime) {
// On that case, re-schedule to the new target
activeTask = this.schedule(undefined, targetTime - now);
return;
}

emit();
}

source.subscribe(
new OperatorSubscriber(
subscriber,
(value: T) => {
lastValue = value;
lastTime = scheduler.now();

// Only set up a task if it's not already up
if (!activeTask) {
activeTask = scheduler.schedule(emitWhenIdle, dueTime);
}
},
undefined,
() => {
// Source completed.
// Emit any pending debounced values then complete
emit();
subscriber.complete();
},
() => {
// Teardown.
lastValue = activeTask = null;
}
)
);
});
}