如何使用 RxJS Observables 制作倒数计时器?

How to make countdown timer with RxJS Observables?

我正在努力使用 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));

To clarify I need help with ReactiveX's RxJS implementation, not the MircoSoft version.

你走在正确的轨道上 - 你的问题是 timer 在原型上不存在(因此在 Observable.range() 上)但在 Observable 上(参见 RxJS docs). I.e. 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));

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

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))

我是 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);
  } );

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

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

Check it out on Stackblitz

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

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
}

这个例子对我有用:)

顺便说一下,使用 takeWhile(val => val >= 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 + current 然后 current 将是-1.

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

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>

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

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

或者这样:)

 interval(1000)
      .take(10)
      .map((t) => Math.abs(t - 10))
      .subscribe((t) => {
        console.log(t);
      });