How to make observable debounceTime conditional

时光毁灭记忆、已成空白 提交于 2020-05-15 05:34:05

问题


I have a simple debounce on an element input event like so:

Observable
        .fromEvent(this.elInput.nativeElement, 'input')
        .debounceTime(2000)
        .subscribe(event => this.onInput(event));

I would like to make the debounce conditional based on the value of the event when emitted, is this possible?

Thanks


回答1:


Yes, that's completely possible. Just make use of the debounce operator instead of debounceTime. It is passed a selector function that receives the previous operators notifaction when invoked.

In your example:

Observable
        .fromEvent(this.elInput.nativeElement, 'input')
        .debounce(ev => ev.hasSomeValue ? timer(2000) : EMPTY)
        .subscribe(event => this.onInput(event));

The selector function expects an ObservableLike and waits for it to emit before forwarding the last notification that debounce received. All other notifications are discarded, like with debounceTime. You can use EMPTY to immediately forward the notifcation without any timeout (though this will be async, see below)

From learn-rxjs:

Though not as widely used as debounceTime, debounce is important when the debounce rate is variable!

Note: Debounce will always asynchronously schedule the forwarding of the last value, even if the inner Observable emits instantly. To avoid this, you'd have to create a second observable and use filter to avoid debounce altogether.




回答2:


Previous answer works if no variables are to be processed by the pipe. Plus passing 0 and asap to debounceTime (as so: debounceTime(0, asap)) does mean the next operator will be immediately called. In fact, once you use debounceTime you may experience a half second delay anyway, which is way too long in some cases.

So, after long time messing up with iif and friends, I decided to copy paste debounceTime and modify it to take a conditional function. Any ideas / improvements are most welcome:

This is intended for Angular 9 and may raise warnings/errors with previous versions.

Use it just like you would with debounceTime, just pass a conditional function as first boolean parameter. If it is true, it will debounce, if false it will immediately continue.

Example:

observable$.pipe(
    conditionalDebounceTime(() => conditionFlag, 1000),
    tap(observedValue ... => ...)
    ...

conditional-debounce-time.ts

import {MonoTypeOperatorFunction, Observable, Operator, SchedulerLike, Subscriber, Subscription, TeardownLogic} from 'rxjs';
import {async} from 'rxjs/internal/scheduler/async';

class ConditionalDebounceTimeSubscriber<T> extends Subscriber<T> {
  private debouncedSubscription: Subscription|null = null;
  private lastValue: T|null = null;
  private hasValue = false;

  constructor(destination: Subscriber<T>,
              private conditionFunc: () => boolean,
              private dueTime: number,
              private scheduler: SchedulerLike) {
    super(destination);
  }

  protected _next(value: T) {
    this.clearDebounce();
    this.lastValue = value;
    this.hasValue = true;
    if (this.conditionFunc()) {
      this.add(this.debouncedSubscription = this.scheduler.schedule(dispatchNext, this.dueTime, this));
    } else {
      (<Subscriber<T>>this.destination).next(this.lastValue);
    }
  }

  protected _complete() {
    this.debouncedNext();
    (<Subscriber<T>>this.destination)!.complete();
  }

  debouncedNext(): void {
    this.clearDebounce();

    if (this.hasValue) {
      const { lastValue } = this;
      // This must be done *before* passing the value
      // along to the destination because it's possible for
      // the value to synchronously re-enter this operator
      // recursively when scheduled with things like
      // VirtualScheduler/TestScheduler.
      this.lastValue = null;
      this.hasValue = false;
      (<Subscriber<T>>this.destination)!.next(lastValue!);
    }
  }

  private clearDebounce(): void {
    const debouncedSubscription = this.debouncedSubscription;

    if (debouncedSubscription !== null) {
      this.remove(debouncedSubscription);
      debouncedSubscription.unsubscribe();
      this.debouncedSubscription = null;
    }
  }
}

class ConditionalDebounceTime<T> implements Operator<T, T> {
  constructor(private conditionFunc: () => boolean, private dueTime: number, private scheduler: SchedulerLike) {
  }

  call(subscriber: Subscriber<T>, source: any): TeardownLogic {
    return source.subscribe(new ConditionalDebounceTimeSubscriber(subscriber, this.conditionFunc, this.dueTime, this.scheduler));
  }
}

export function conditionalDebounceTime<T>(conditionFunc: () => boolean, dueTime: number, scheduler: SchedulerLike = async): MonoTypeOperatorFunction<T> {
  return (source: Observable<T>) => source.lift(new ConditionalDebounceTime(conditionFunc, dueTime, scheduler));
}

function dispatchNext(subscriber: ConditionalDebounceTimeSubscriber<any>) {
  subscriber.debouncedNext();
}


来源:https://stackoverflow.com/questions/53044981/how-to-make-observable-debouncetime-conditional

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!