【问题标题】:pause/resume a timer Observable暂停/恢复计时器 Observable
【发布时间】:2019-03-06 23:13:19
【问题描述】:

我正在用 angular/rxjs6 构建一个简单的秒表,我可以启动计时器,但无法暂停/恢复它。

  source: Observable<number>;
  subscribe: Subscription;

  start() {
    this.source = timer(0, 1000);
    this.subscribe = this.source
      .subscribe(number => {
        this.toSeconds = number % 60;
        this.toMinutes = Math.floor(number / 60);
        this.toHours = Math.floor(number / (60 * 60));

        this.seconds = (this.toSeconds < 10 ? '0' : '') + this.toSeconds;
        this.minutes = (this.toMinutes < 10 ? '0' : '') + this.toMinutes;
        this.hours = (this.toHours < 10 ? '0' : '') + this.toHours;
    });
  }

  pause() {
    this.subscribe.unsubscribe(); // not working
  }

经过大量搜索,我发现我应该使用switchMap 运算符来完成此操作,但我是 rxjs 新手,不知道如何正确操作。

任何帮助将不胜感激。

【问题讨论】:

    标签: angular angular6 rxjs6


    【解决方案1】:

    我今天也遇到了同样的问题(使用 Angular 实现俄罗斯方块克隆时)。这是我最终得到的结果:

    import { Subject, timer } from 'rxjs';
    
    export class Timer {
      private timeElapsed = 0;
      private timer = null;
      private subscription = null;
    
      private readonly step: number;
    
      update = new Subject<number>();
    
      constructor(step: number) {
        this.timeElapsed = 0;
        this.step = step;
      }
    
      start() {
        this.timer = timer(this.step, this.step);
        this.subscription = this.timer.subscribe(() => {
          this.timeElapsed = this.timeElapsed + this.step;
          this.update.next(this.timeElapsed);
        });
      }
    
      pause() {
        if (this.timer) {
          this.subscription.unsubscribe();
          this.timer = null;
        } else {
          this.start();
        }
      }
    
      stop() {
        if (this.timer) {
          this.subscription.unsubscribe();
          this.timer = null;
        }
      }
    }
    

    在我的游戏服务中,我是这样使用它的:

      init() {
        this.timer = new Timer(50);
        this.timer.start();
        this.timer.update.subscribe(timeElapsed => {
          if (timeElapsed % 1000 === 0) {
            this.step(); // step() runs one game iteration
          }
        });
      }
    
      togglePause() {
        this.timer.pause();
      }
    

    注意:我是 Angular/RxJS 的新手,所以我不确定上面的代码是否良好。但它有效。

    【讨论】:

    • 使用 rxjs 计时器启动/停止/暂停的完美示例,谢谢!
    • @Danilo 谢谢,不过请注意我帖子的最新句子。 :) 我可以确认它有效,但我不确定它实际上是否正确,也不确定它是否遵循最佳实践。
    【解决方案2】:

    这是一个使用 rxjs 6 的 node.js sn-p。除非按下 p,否则将发出计时器事件。再次按下时,发射继续(ctrl-c 将退出)。

    在内部,当暂停器发出false 时,实际上启动了一个新计时器。因此,我们在 (concat) 暂停器前面加上 false 发射来启动第一个计时器。 2 个 scan 运算符管理链的状态(暂停切换器 + 计数器)。

    import { timer, concat, NEVER, of, fromEvent } from 'rxjs';
    import { scan, tap, filter, switchMap } from 'rxjs/operators';
    import { emitKeypressEvents } from 'readline';
    
    process.stdin.setRawMode(true);
    emitKeypressEvents(process.stdin);
    
    const keypresses$ = fromEvent(process.stdin, 'keypress', (_, key) => key);
    const pauser$ = keypresses$.pipe(
      tap(key => {
        if (key && key.ctrl && key.name == 'c') {
          process.exit(0);
        }
      }),
      filter(key => key.name === 'p'),
      scan(acc => !acc, false),
    );
    
    const starter$ = of(false);
    concat(starter$, pauser$)
      .pipe(
        switchMap(stopped => (stopped ? NEVER : timer(0, 1000))),
        scan(acc => acc + 1, 0),
      )
      .subscribe(console.log);
    

    【讨论】:

    • 这个答案似乎是我正在寻找的。但是,我尝试在 this articlethis answer 的帮助下以角度实现它,但没有运气。如果你能帮助我用我在问题中发布的代码来实现它,那就太好了。谢谢
    • 您需要使您的“暂停/取消暂停”操作成为可观察的(现在它们似乎是被调用的函数,我想在按钮上单击),然后您可以switchMap pause/unpause行动,对于计时器,这就是想法。当你有一个pauser$ observable 时,你应该可以使用上面的代码。
    【解决方案3】:

    我从未使用过timer() 函数,但您可以设置一个这样的标志。

      source: Observable<number>;
      subscribe: Subscription;
      timerPaused: boolean = false;
    
    
      start() {
        this.seconds = 0;
        this.minutes = 0;
        this.hours = 0;
        this.time = 0;
    
        this.source = timer(0, 1000);
        this.subscribe = this.source
          .subscribe(number => {
            if(!this.timerPaused) {
              this.toSeconds = this.time % 60;
              this.toMinutes = Math.floor(this.time / 60);
              this.toHours = Math.floor(this.time / (60 * 60));
    
              this.seconds = (this.toSeconds < 10 ? '0' : '') + this.toSeconds;
              this.minutes = (this.toMinutes < 10 ? '0' : '') + Math.floor(number / 60);
              this.hours = (this.toHours < 10 ? '0' : '') + Math.floor(number / (60 * 60));
              this.time += 1000
            }
          });
      }
    
      onPause() {
        this.timerPaused = true;
      }
    
      onResume() {
        this.timerPaused = false;
      }
    
      ngOnDestroy() {
        this.subscribe.unsubscribe();
      }
    

    【讨论】:

    • 这不是暂停计时器 Observable 本身,而是我使用的局部变量。如果我将计时器暂停 15 秒并等待 5 秒再启动它,它会以 20 秒而不是 16 秒开始。因为可观察的计时器本身仍在运行
    • 像我刚刚更新的那样添加一个时间变量怎么样?我见过的所有使用timer().setInterval() 的例子都是这样做的。我不知道有什么方法可以真正暂停订阅。
    • 其实,我找到了一种使用switchMap 运算符的方法,但我不能在这里应用它。如果我找不到暂停/恢复 Observable 的方法,我将改用 setInterval。非常感谢@rhavelka
    【解决方案4】:

    为了任何搜索的人的利益,这是我实现的可暂停 rxjs 计时器。它需要一个可观察的暂停,以便您可以随意暂停/取消暂停。

    用法:

    pausableTimer(1000, this.myPauseObservable)
      .subscribe(() => { /* do your thing */ };
    

    代码:

    import { BehaviorSubject, combineLatest, Observable, timer } from 'rxjs';
    import { delayWhen, startWith } from 'rxjs/operators';
    
    export function pausableTimer(
      delayMs: number = 0,
      pause: Observable<boolean>
    ): Observable<number> {
      return new Observable<number>((observer: any) => {
        // The sequence of delays we'd like to listen to.
        // Start off with zero & paused = false so that combineLatest always fires at least once.
        const delays = new BehaviorSubject(0);
        pause = pause.pipe(startWith(false));
    
        // Piped delays that emit at the time given.
        const temporalDelays = delays.pipe(delayWhen((val) => timer(val)));
    
        let startTime = Date.now();
        let totalPauseTimeMs = 0;
        let lastPaused: number | undefined;
    
        // This listens to pause/unpause events, as well as timers.
        const sub = combineLatest([pause, temporalDelays]).subscribe(
          ([paused, delay]) => {
            if (paused) {
              // If we're paused, we never want to complete, even if the timer expires.
              if (lastPaused === undefined) {
                lastPaused = Date.now();
              }
    
              return;
            }
    
            // If we've just un-paused, add on the paused time.
            if (lastPaused !== undefined) {
              totalPauseTimeMs += Date.now() - lastPaused;
              lastPaused = undefined;
            }
    
            // Look at how much time has expired.
            const totalElapsed = Date.now() - startTime;
            const remainingTime = delayMs + totalPauseTimeMs - totalElapsed;
    
            if (remainingTime <= 0) {
              // We're done!
              observer.next(totalElapsed);
              observer.complete();
            } else {
              // We're not done.  If there's not already a timer running, start a new one.
              const lastTimerFinished = delay === delays.value;
              if (lastTimerFinished) {
                delays.next(remainingTime);
              }
            }
          }
        );
    
        observer.add(() => sub.unsubscribe());
      });
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多