I'm using a stream which is throttled when I scroll the window.
While throttling (as long as scrolling), it emits values to the console.
However , when stream is idle (user is not scrolling the window) - I want a timer to kick in. However - if the user starts scrolling again - I don't want that timer to emit values.
Currently I'm doing this :
const observable = Rx.Observable.fromEvent(window, 'scroll');
const subscriber = observable
.throttleTime(300 )
.map(() => 'throttle')
.merge(Rx.Observable.interval(1000).map(() => 'tick') )
.subscribe(
(x) => {
console.log('Next: event!', x);
},
(err) => {
console.log('Error: %s', err);
},
() => {
console.log('Completed');
});
The problem is that , while scrolling - I see both "throttle"
AND "tick"
( I should only see "throttle")
Think of this from another POV. A job always has to run. If I scroll - that throttled scroll - should invoke the job. If I don't scroll - a timer should kick in and start doing the job . (and stops if user start scrolling again).
Question:
How can I start a timer after an idle time of not scrolling ?
You can use debounceTime
to detect periods without scrolling.
const scroll = Rx.Observable.fromEvent(window, 'scroll')
.throttleTime(300)
.mapTo(false);
const noscroll = Rx.Observable.fromEvent(window, 'scroll')
.startWith(0) // init with no scroll.
.debounceTime(300) // detect no scroll after 300 ms.
.mapTo(true);
scroll.merge(noscroll)
.switchMap(e => e ? Rx.Observable.interval(1000).mapTo("Tick!") : Rx.Observable.of("Scroll!"))
// start the interval if there was no scroll. Stop the interval if there was a scroll.
.subscribe(updateTimer)
Another problem with your code is using merge
that will keep both sources subscribed, instead i use switchMap
(a sibling of mergeMap
) that will subscribe to the inner observable each time a new event is emitted, but also unsubscribe the previous inner source if another event is emitted from the source.
Re: "another POV" part of the question: You can replace Rx.Observable.interval(1000)
in switchMap
with the job. Scrolling will cancel/unsubscribe the job (as empty
is emitted), if there is no more scrolling, the job will start again.
I'd do it like this:
const scroll$ = Rx.Observable.fromEvent(window, 'scroll')
.throttleTime(300 /* ms */)
.publish();
scroll$.connect();
const subscriber = scroll$
.map(() => 'throttle')
.race(Rx.Observable.interval(1000).map(() => 'tick'))
.take(1)
.repeat()
.subscribe(
(x) => {
console.log('Next: event!', x);
},
(err) => {
console.log('Error: %s', err);
},
() => {
console.log('Completed');
});
This uses the race()
operator to subscribe only to the Observable that emits first which is the 1s interval
or the scroll event. Right after that I want to start this again with another interval so I use take(1).repeat()
.
I also had to turn the scroll$
Observable into a hot Observable to keep the throttleTime()
running among the repeated subscriptions.
Your updated demo: https://plnkr.co/edit/sWzSm32uoOQ1hOKigo4s?p=preview
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.