我需要在我们的 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')));
现在,这只是用户界面上偶尔跳过一秒钟的小麻烦,但尽管如此,我还是希望看到它能正常工作。也许还有更合适的管道来实现这一目标。
我不确定,但你可能正在寻找这样的东西:
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!'),
});