【问题标题】:RXJS - start a timer only when idle?RXJS - 仅在空闲时启动计时器?
【发布时间】:2017-09-17 11:43:29
【问题描述】:

我使用的流在我滚动窗口时会受到限制。
在节流时(只要滚动),它会向控制台发出值。

但是,当流空闲时(用户滚动窗口) - 我想要一个计时器启动。但是 - 如果用户再次开始滚动 - 我不希望那个计时器发出值。

目前我正在这样做:

  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');
          });

问题是,在滚动时 - 我同时看到 "throttle""tick"(我应该只看到“油门”)

从另一个 POV 考虑这个问题。工作总是要运行。如果我滚动 - 那个受限制的滚动 - 应该调用该作业。如果我不滚动 - 计时器应该启动并开始工作。 (如果用户再次开始滚动则停止)。

问题:
如何在不滚动的空闲时间后启动计时器?

PLNKR

【问题讨论】:

  • 这是如何在 SO 上提出一个好的问题。 +1
  • 回复your other comment:您可以使用exhaustMap + debounceTime 来检测没有滚动的技巧来解决这个问题。我在这里使用switchMap 所做的工作是因为我们在事件爆发期间什么都不做,而工作发生在流空闲时。反过来,switchMap 会在每次发出新的滚动事件时重新启动作业(计时器)。

标签: javascript rxjs rxjs5


【解决方案1】:

您可以使用debounceTime 无需滚动即可检测经期。

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)

您的代码的另一个问题是使用merge,这将使两个源都被订阅,而不是我使用switchMapmergeMap 的兄弟),它将在每次发出新事件时订阅内部可观察对象,但是如果从源发出另一个事件,也会取消订阅先前的内部源。

回复:问题的“另一个 POV”部分:您可以将 switchMap 中的 Rx.Observable.interval(1000) 替换为工作。滚动将取消/取消订阅作业(因为发出了empty),如果不再滚动,作业将重新开始。

Live demo

【讨论】:

【解决方案2】:

我会这样做:

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');
        });

这使用race() 运算符仅订阅首先发出的Observable,即1s interval 或滚动事件。之后我想用另一个间隔重新开始,所以我使用take(1).repeat()

我还必须将 scroll$ Observable 转换为热门 Observable 以保持 throttleTime() 在重复订阅中运行。

您更新的演示:https://plnkr.co/edit/sWzSm32uoOQ1hOKigo4s?p=preview

【讨论】:

  • 请问publish().Connect()publish().refcount() 之间的区别是什么?publish().refcount() {这是一个share()}
  • @Royi connect() 不是每个说的运算符(它不返回 Observable)。它只是ConnectableObservable 类上的一个方法,它返回一个Subscription,您可以使用它来“断开连接”。它类似于subscribe() 方法,您可以使用运算符链接,但它也返回Subscription。这意味着它只能在运算符链的末端使用。另一方面,refCount() 返回另一个 Observable,因此您可以将其与其他运算符链接。
  • Martin,你已经通过 connect 让这个 observable 成为了热门。但是如果我将其更改为.share()(这也是使其变热的另一种方式)-然后-滚动时-似乎它不尊重throttle延迟。这是为什么 ? plnkr.co/edit/jjWUxbk9fSItLT1U7uiq?p=preview
  • @Royi share() 不会把它变成一个 hot Observable。这只是publish().refCount() 的快捷方式,refCount() 根据观察者的数量订阅/取消订阅其源 Observable。由于我们使用重复订阅来使用race() 运算符,因此我们需要确保我们始终被订阅。这就是我们手动调用connect() 的原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-06
  • 1970-01-01
  • 2021-11-03
相关资源
最近更新 更多