RxJS operator that waits for quiet period in event stream, but in case of busy event stream does not wait for ever

帅比萌擦擦* 提交于 2019-12-22 10:50:56

问题


The scenario:

  • I have a stream of events, each event should result in the updated display of information (event stream is from websockets and the display is in a highcharts chart, but that is not important)
  • For performance reasons, I do not want to trigger a UI update for each event.
  • I would rather do the following:

    • When I receive an event I want to do the UI update only it is more than X milliseconds since the last update
    • But every Y milliseconds (Y > X) I want to do an update anyway, if there have been any incoming events
    • So I am looking for some kind of (combination of) RxJS operator that will rate-limit the event stream to emit an event only when a quiet period occurs (or the wait-for-quiet-period-maximum-time has been exceeded).
    • I.e. I want to wait for quiet periods, but not for ever.

How can I implement what I describe above?

I have looked at:

  • https://rxjs-dev.firebaseapp.com/api/operators/sampleTime
  • https://rxjs-dev.firebaseapp.com/api/operators/debounceTime
  • ... and some other rxjs time/rate-limiting operators

回答1:


You can write an operator to do what you want by using debounce and employing two timers in the composition of the notifier observable:

  • a timer that emits X milliseconds after the source emits a value; and
  • a timer that emits Y milliseconds after the observable returned by the operator emits a value.

See the snippet below. Comments within should explain how it works.

const {
  ConnectableObservable,
  merge,
  MonoTypeOperatorFunction,
  Observable,
  of,
  Subject,
  Subscription,
  timer
} = rxjs;

const {
  concatMap,
  debounce,
  mapTo,
  publish,
  startWith,
  switchMap
} = rxjs.operators;

// The pipeable operator:

function waitUntilQuietButNotTooLong(
  quietDuration,
  tooLongDuration
) {

  return source => new Observable(observer => {

    let tooLongTimer;
    
    // Debounce the source using a notifier that emits after `quietDuration`
    // milliseconds since the last source emission or `tooLongDuration`
    // milliseconds since the observable returned by the operator last
    // emitted.

    const debounced = source.pipe(
      debounce(() => merge(
        timer(quietDuration),
        tooLongTimer
      ))
    );

    // Each time the source emits, `debounce` will subscribe to the notifier.
    // Use `publish` to create a `ConnectableObservable` so that the too-long
    // timer will continue independently of the subscription from `debounce`
    // implementation.

    tooLongTimer = debounced.pipe(
      startWith(undefined),
      switchMap(() => timer(tooLongDuration)),
      publish()
    );

    // Connect the `tooLongTimer` observable and subscribe the observer to
    // the `debounced` observable. Compose a subscription so that
    // unsubscribing from the observable returned by the operator will
    // disconnect from `tooLongTimer` and unsubscribe from `debounced`.

    const subscription = new Subscription();
    subscription.add(tooLongTimer.connect());
    subscription.add(debounced.subscribe(observer));
    return subscription;
  });
}

// For a harness, create a subject and apply the operator:

const since = Date.now();
const source = new Subject();
source.pipe(
  waitUntilQuietButNotTooLong(100, 500)
).subscribe(value => console.log(`received ${value} @ ${Date.now() - since} ms`));

// And create an observable that emits at a particular time and subscribe
// the subject to it:

const emissions = of(0, 50, 100, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000, 1050, 1100, 1150);
emissions.pipe(
  concatMap((value, index) => timer(new Date(since + value)).pipe(
    mapTo(index)
  ))
).subscribe(source);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs@6/bundles/rxjs.umd.min.js"></script>



回答2:


You can combine a timer with a debounceTime and use it to sample the original event stream:

let $tick = Rx.Observable.timer(100, 100);
let $updates = $events
                  .sample($tick.merge($events.debounceTime(30))
                  .distinctUntilChanged();

This will take an event every 100 milliseconds, but also if an event occurs before a 30 millisecond gap.

With sample, the values in the sampling stream are ignored. So this technique creates a sampling stream that includes both the time-based requirement and also the debouncing. Whenever either of these happens, it will take the latest value from the original stream.

Using distinctUntilChanged prevents events repeating consecutively with the same value if nothing changed. You may need to add a comparison function as an argument to distinctUntilChanged if your data is structured or otherwise can't be compared with ===.



来源:https://stackoverflow.com/questions/50643440/rxjs-operator-that-waits-for-quiet-period-in-event-stream-but-in-case-of-busy-e

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