【问题标题】:Pause an interval rxjs暂停一个间隔 rxjs
【发布时间】:2021-02-16 22:39:15
【问题描述】:

我有一个简单的秒表,使用 rxjs

问题是: 不知道如何暂停我的间隔流,然后继续它 stackbiz

【问题讨论】:

  • This answer 可能就是您要找的。​​span>
  • 我只是重构我的服务,添加一些 if 语句,然后取消订阅我的流,然后,当我需要它时,我订阅了 time.service.ts github

标签: angular typescript asynchronous rxjs


【解决方案1】:

我经常看到秒表问题出现,以至于我认为创建一个自定义的秒表 observable 会很有趣。 RxJS 方式是通过切换进出定时器/间隔来实现这一点。

另一种有趣的实现方式是使用 setTimeout。 setTimeout 实际上应该需要更少的内存,因为我们不依赖可观察设备来完成我们的计时目标

这将如何运作?我们自定义的 observable 创建了一个流,它输出秒表上的数字,并由一个单独的流控制(这里称为control$)。因此,当control$ 发出“START”时,秒表启动,当它发出“STOP”时,秒表停止,当它发出“RESET”时,秒表将计数器设置回零。当control$ 出错或完成时,秒表出错或完成。

用 switchMap 和 Timer 实现

function createStopwatch(control$: Observable<string>, interval = 1000): Observable<number>{
  return defer(() => {
    let toggle: boolean = false;
    let count: number = 0;

    const endTicker$ = new Subject();

    const ticker = () => {
      return timer(0, interval).pipe(
        takeUntil(endTicker$),
        map(x => count++)
      )
    }
  
    return control$.pipe(
      tap({
        next: _ => {/*Do nothing*/},
        complete: () => {
          endTicker$.next();
          endTicker$.complete();
        },
        error: err => {
          endTicker$.next();
          endTicker$.complete();
        }
      }),
      filter(control => 
        control === "START" ||
        control === "STOP" ||
        control === "RESET"
      ),
      switchMap(control => {
        if(control === "START" && !toggle){
          toggle = true;
          return ticker();
        }else if(control === "STOP" && toggle){
          toggle = false;
          return EMPTY;
        }else if(control === "RESET"){
          count = 0;
          if(toggle){
            return ticker();
          }
        }
        return EMPTY;
      })
    );
  });
}

用 setTimeout 实现

function createStopwatch(control: Observable<string>, interval = 1000): Observable<number> {
  return new Observable(observer => {
    let count: number = 0;
    let tickerId: number = null;

    const clearTicker = () => {
      if(tickerId != null){
          clearTimeout(tickerId);
          tickerId = null;
        }
    }
    const setTicker = () => {
      const recursiveTicker = () => {
        tickerId = setTimeout(() => {
          observer.next(count++);
          recursiveTicker();
        }, interval);
      }
      clearTicker();
      observer.next(count++);
      recursiveTicker();
    }

    control.subscribe({
      next: input => {
        if(input === "START" && tickerId == null){
          setTicker();
        }else if(input === "STOP"){
          clearTicker();
        }else if(input === "RESET"){
          count = 0;
          if(tickerId != null){
            setTicker();
          }
        }
      },
      complete: () => {
        clearTicker();
        observer.complete();
      },
      error: err => {
        clearTicker();
        observer.error(err);
      }
    });
  
    return {unsubscribe: () => clearTicker()};
  });
}

使用中的秒表

这是一个使用这个 observable 的例子。我通过主题管理控制流,但它可以很容易地被合并/映射 DOM 事件或类似的东西。

const control$ = new Subject<string>();
createStopwatch(control$, 250).subscribe(console.log);

// We send a new action to our control stream every 1 second
const actions = ["START", "STOP", "START", "RESET", "START"]

zip(from(actions), interval(1000)).pipe(
  map((x,y) => x),
  finalize(() => {
    // After 5 seconds, unsubscribe via the control
    // If our control finishes in any way (
    // completes, errors, or is unsubscribed), our
    // sopwatch reacts by doing the same.
    control$.complete();
  })
).subscribe(x => control$.next(x));

使用中的秒表 #2

这用setTimeout而不是interval来控制秒表。

const control$ = new Subject<string>();
createStopwatch(control$, 250).subscribe(console.log);

// We send a new action to our control stream every 1 second
const actions = ["START", "STOP", "START", "RESET", "START"]

actions.forEach((val, index) => {
  setTimeout(() => {
    control$.next(val);
  },
  index * 1000);
})

// Unsubscribe via the control
setTimeout(() => {
  control$.complete();
}, actions.length * 1000);

【讨论】:

  • 你可以查看我的秒表,github 很高兴得到你的反馈
  • 我总是尽量避免 setTimeout;) 不知道为什么,但我认为总有更好的方法来使用某些东西而不是 setTO
  • 我怀疑这是一种很好的直觉 :)。在此示例中,setTimeout 解决方案比switchMaptimer 解决方案具有更复杂的控制流。只是停止计时器的额外内容/查看control$ 的结尾会扼杀更多 RxJS 解决方案的一些优雅。
【解决方案2】:

看看我在这个stackblitz中的解决方案

组件必须订阅 StopWatch-Service。我不喜欢给服务一个价值,然后服务隐含地改变价值的想法。因此,我使用一种明确的方式来获取更新的秒表(作为可观察的)。

我使用“timer”而不是“interval”,因为“interval”会在一秒钟后发出第一个值(0),因此我的 stopWath 会有延迟,

有个小技巧。有一个私有变量“timer$”,一个 BehaviorSubject。一旦计数器启动,我就会启动一个计时器并订阅它。在该订阅中,每次发出计时器,都会为 BehaviorSubject 发出一个新值。

现在我也存储订阅。
如果我想停止一切,我只需取消订阅“计时器”。结果时钟停止了。但是因为它与 behaviorSubject timer$ 有点分离,所以仍然会存储最后一个值。

或者换个说法:
BehaviorSubject 永远存在,消费者永远不会取消订阅(只有当他自己这样做时)。每当计数开始或停止时,我都会将计时器功能“附加”和“取消附加”。

顺便说一句,当组件被销毁时,始终确保组件取消订阅所有正在运行的可观察对象是一个好习惯。如果不这样做,那么那些仍然活跃的订阅可能会导致应用程序出现性能甚至更严重的问题。

希望对你有帮助

热烈的问候

【讨论】:

  • 感谢您分享您的答案,有我的秒表stopwatch 很高兴获得您的反馈;
  • 顺便说一句,你有和我一样的错误,因为它我创建了这个问题))尝试按几次开始按钮,每次点击你都会创建一个新的订阅;
猜你喜欢
  • 1970-01-01
  • 2017-04-11
  • 2018-03-17
  • 1970-01-01
  • 2021-08-31
  • 1970-01-01
  • 2019-10-04
  • 2021-11-09
  • 2021-02-19
相关资源
最近更新 更多