【问题标题】:How to make countdown timer with RxJS Observables?如何使用 RxJS Observables 制作倒数计时器?
【发布时间】:2016-04-27 13:38:58
【问题描述】:

我正在努力使用 Observables 创建倒数计时器,http://reactivex.io/documentation/operators/timer.html 的示例似乎不起作用。在此特定示例中,与 timerInterval 相关的错误不是从计时器返回的 Observable 的函数。

我也一直在尝试其他方法,我想出的最好的方法是:

Observable.interval(1000).take(10).subscribe(x => console.log(x));

这里的问题是它从 0 计数到 10,我想要一个倒数计时器,例如10,9,8...0.

我也试过这个,但 timer 不存在 Observable 类型

Observable.range(10, 0).timer(1000).subscribe(x => console.log(x));

以及,它根本不产生任何输出。

Observable.range(10, 0).debounceTime(1000).subscribe(x => console.log(x));

为了澄清我需要 ReactiveX 的 RxJS 实现的帮助,而不是 MircoSoft 版本。

【问题讨论】:

  • 请记住,任何不断调用timer(1000) 的计时器都会随着时间的推移而漂移。短时间内可以,但如果您正在编程时钟,那就不行了!如果您需要准确性,则需要使用系统时钟来计算时间偏移量。

标签: rxjs observable reactive-programming


【解决方案1】:

这是最简单的方法恕我直言:

import { interval } from 'rxjs'
import { map, take } from 'rxjs/operators'

const durationInSeconds = 1 * 60 // 1 minute

interval(1000).pipe(take(durationInSeconds), map(count => durationInSeconds - count)).subscribe(countdown => {
  const hrs  = (~~(countdown / 3600)).toString()
  const mins = (~~((countdown % 3600) / 60)).toString()
  const secs = (~~countdown % 60).toString()
  console.log(`${hrs.padStart(2, '0')}:${mins.padStart(2, '0')}:${secs.padStart(2, '0')}`);
})

【讨论】:

    【解决方案2】:

    我还需要一个倒数的间隔,所以我尝试了这个解决方案:

    const { interval, take } = rxjs
    
    const countDownEl = document.querySelector("#countdown");
    
    /**
     * Coundown with RxJs
     * @param startPoint {number} Value of timer continuing to go down
     */
    function countDown(startPoint) {
      // * Fire Every Second
      const intervalObs = interval(1000);
    
      // * Shrink intervalObs subscription
      const disposeInterval = intervalObs.pipe(take(startPoint));
    
      // * Fire incremental number on every second
    
      disposeInterval.subscribe((second) => {
        console.log("Second: ", second);
        countDownEl.innerHTML = startPoint - second;
      })
    }
    
    countDown(10);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.3.0/rxjs.umd.min.js"></script>
    
    <label>Countdown: <span id="countdown"></span></label>

    【讨论】:

      【解决方案3】:

      这个例子对我有用:)

      顺便说一句,使用takeWhile(val =&gt; val &gt;= 0) 代替take(someNumber) 可能有意义,但它会检查-1,然后才完成.. 1 秒太晚了。

      下面的示例将发出 10、9、8、7、6、5、4、3、2、1、0。从 10 开始并立即以 0 结束似乎微不足道,但对我来说相当棘手。

      const counter$ = interval(1000); // rxjs creation operator - will fire every second
      const numberOfSeconds = 10;
      
      counter$.pipe(
          scan((accumulator, _current) =>  accumulator - 1, numberOfSeconds + 1),
          take(numberOfSeconds + 1),
      
          // optional
          finalize(() => console.log(
            'something to do, if you want to, when 0 is emitted and the observable completes'
          ))
      )
      

      这将做同样的事情:

      
      counter$.pipe(
          scan((accumulator, current) => accumulator - 1, numberOfSeconds),
          startWith(numberOfSeconds), // scan will not run the first time!
          take(numberOfSeconds + 1),
      
          // optional
          finalize(() => console.log(
            'something to do, if you want to, when 0 is emitted and the observable completes'
          ))
        )
      
      

      当然,您可以进行许多更改。例如,您可以在scan 之前mapTo(-1),然后写accumulator + currentcurrent 将是-1。

      【讨论】:

        【解决方案4】:

        我的倒计时功能与显示时间:

        import { Observable, timer, of, interval } from "rxjs";
        import { map, takeWhile, take } from "rxjs/operators";
        
        function countdown(minutes: number, delay: number = 0) {
           return new Observable<{ display: string; minutes: number; seconds: number }>(
              subscriber => {
                timer(delay, 1000)
                  .pipe(take(minutes * 60))
                  .pipe(map(v => minutes * 60 - 1 - v))
                  .pipe(takeWhile(x => x >= 0))
                  .subscribe(countdown => { // countdown => seconds
                    const minutes = Math.floor(countdown / 60);
                    const seconds = countdown - minutes * 60;
        
                    subscriber.next({
                      display: `${("0" + minutes.toString()).slice(-2)}:${("0" + seconds.toString()).slice(-2)}`,
                      minutes,
                      seconds
                    });
        
                    if (seconds <= 0 && minutes <= 0) {
                      subscriber.complete();
                    }
               });
           });
        }
        
        countdown(2).subscribe(next => {
          document.body.innerHTML = `<pre><code>${JSON.stringify(next, null, 4)}</code></pre>`;
        });
        

        输出即:

        {
           "display": "01:56",
           "minutes": 1,
           "seconds": 56
        }
        
        【解决方案5】:

        使用timerscantakeWhile 如果您不想依赖变量作为开始时间,scan 中的第三个参数是开始编号

        timer$ = timer(0, 1000).pipe(
          scan(acc => --acc, 120),
          takeWhile(x => x >= 0)
        );
        

        Check it out on Stackblitz

        【讨论】:

        • 此计时器将延迟一秒结束,因为它检查了 -1,然后才完成。使用 take(120) 而不是 takeWhile() 会很有效。
        【解决方案6】:

        我是take...()的情人,所以我使用takeWhile()如下(RxJS 6.x.x,ES6方式)

        import {timer} from 'rxjs';
        import {takeWhile, tap} from 'rxjs/operators';
        
        
        let counter = 10;
        timer(1000, 1000) //Initial delay 1 seconds and interval countdown also 1 second
          .pipe(
            takeWhile( () => counter > 0 ),
            tap(() => counter--)
          )
          .subscribe( () => {
            console.log(counter);
          } );
        

        【讨论】:

          【解决方案7】:

          使用间隔,允许您指定一秒有多长

          const time = 5 // 5 seconds
          var timer$ = Rx.Observable.interval(1000) // 1000 = 1 second
          timer$
            .take(time)
            .map((v)=>(time-1)-v) // to reach zero
            .subscribe((v)=>console.log('Countdown', v))
          

          【讨论】:

            【解决方案8】:

            你走在了正确的轨道上——你的问题是timer 不存在于原型上(因此存在于Observable.range() 上)但存在于 Observable 上(参见 RxJS docs)。 IE。 jsbin

            const start = 10;
            Rx.Observable
              .timer(100, 100) // timer(firstValueDelay, intervalBetweenValues)
              .map(i => start - i)
              .take(start + 1)
              .subscribe(i => console.log(i));
            
            // or
            Rx.Observable
              .range(0, start + 1)
              .map(i => start - i)
              .subscribe(i => console.log(i));
            

            【讨论】:

            • 感谢您的建议。它确实有效,只是感觉应该有更简单的方法来使用 Observables 做到这一点。理想情况下,一个迭代器运算符允许向下计数,而不是仅递增的 range(start, count)。
            • 希望其他人可以提供一种方法。在那之前:您是否考虑过扩展 Observable 的原型以隐藏实现(例如like this)?
            • 有一个操作员这样做,generate 它只是还没有被添加到新项目中。
            • 我在 v4 中看到了 generate 函数,我很惊讶它不在 v5 中。它会为我提供我需要的功能。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-06-14
            相关资源
            最近更新 更多