问题
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