实现一个倒计时器,定期与RxJs中的后端同步

问题描述 投票:0回答:1

我需要在我们的 Angular 组件之一中向用户显示剩余时间。剩余时间的初始值来自服务器。使用

interval
这将是一项微不足道的任务,但出于某种原因,我们希望定期与后端重新同步时间,以防浏览器停滞或其他情况。我知道这可能不是完全必要的,但我认为这也是一个有趣的问题,并且希望看到它实现。

当前的代码工作得很好,但是递归地使用

setTimeout
是相当必要的,但我很好奇让它以 RxJs 的方式工作。


它的工作方式是正确倒计时并与服务器同步,但每次我同步时都会有一个错过的刻度,这意味着用户界面上的时间显示会跳过一秒,直到再次显示正确的时间。我认为与以某种方式启动嵌套间隔或服务器请求延迟有关,但找不到解决方法。

const sync$ = this.http.get<number>(`/app/time/${this.hash}`); // this fetches the server time (seconds remaining)
const subject = new Subject<number>();

interval(this.syncInterval * 1000) // outer stream, runs every n seconds to sync with server
    .pipe(
        startWith(0),
        switchMap(() =>
            sync$.pipe(
                switchMap((timeLeft) =>
                    interval(1000).pipe( // nested stream for just downcounting the seconds betweeen syncs 
                        map((n) => timeLeft - n),
                        take(this.syncInterval),
                    ),
                ),
            ),
        ),
    )
    .subscribe(this.subject);

// this goes to UI via async pipe
this.remainingTime$ = this.subject.pipe(map((n) => Duration.fromObject({ seconds: n }).toFormat('hh:mm:ss'))); 

现在,这只是用户界面上偶尔跳过一秒钟的小麻烦,但尽管如此,我还是希望看到它能正常工作。也许还有更合适的管道来实现这一目标。

angular rxjs
1个回答
0
投票

我不确定,但你可能正在寻找这样的东西:

import {
  map,
  interval,
  Subject,
  takeWhile,
  timer,
  tap,
  BehaviorSubject,
  switchMap,
  Observable,
} from 'rxjs';

function countRemainingTime(remainingTimeUpdater$: Observable<number>) {
  /**
   * seconds
   */
  let remainingTime: number;
  return remainingTimeUpdater$.pipe(
    tap((value) => {
      console.log('Setting new remaining time', value);
      remainingTime = value;
    }),
    switchMap(() => interval(1000)),
    map(() => --remainingTime),
    takeWhile((value) => value > 0)
  );
}

/**
 * seconds
 */
const remainingTimeUpdater$ = new BehaviorSubject<number>(1000);

const counter$ = countRemainingTime(remainingTimeUpdater$);
counter$.subscribe({
  next: console.log,
  error: console.error,
  complete: () => console.warn('Countdown completed!'),
});

/**
 * Updating from the backend
 */

interval(4000)
  .pipe(
    tap(() => {
      remainingTimeUpdater$.next(Math.floor(Math.random() * 10));
    }),
    takeWhile(() => remainingTimeUpdater$.getValue() > 0)
  )
  .subscribe({
    complete: () => console.warn('Updating from the backend completed!'),
  });

参见stackblitz

© www.soinside.com 2019 - 2024. All rights reserved.