最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - RXJS - start a timer only when idle? - Stack Overflow

programmeradmin1浏览0评论

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 ?

PLNKR

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 ?

PLNKR

Share Improve this question edited Apr 23, 2017 at 7:20 Royi Namir asked Apr 20, 2017 at 17:38 Royi NamirRoyi Namir 149k144 gold badges493 silver badges831 bronze badges 2
  • 2 This is how to make a good question on SO. +1 – kind user Commented Apr 20, 2017 at 17:38
  • Re your other ment: You could solve this using the trick there using exhaustMap + debounceTime to detect no scroll. What i did here with switchMap works because we do nothing during the burst of events and the work happens when the stream is idle. The other way around, switchMap would restart the job(timer) each time a new scroll event is emitted. – Dorus Commented Apr 20, 2017 at 20:13
Add a ment  | 

2 Answers 2

Reset to default 3

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.

Live demo

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

发布评论

评论列表(0)

  1. 暂无评论