我经常看到秒表问题出现,以至于我认为创建一个自定义的秒表 observable 会很有趣。 RxJS 方式是通过切换进出定时器/间隔来实现这一点。
另一种有趣的实现方式是使用 setTimeout。 setTimeout 实际上应该需要更少的内存,因为我们不依赖可观察设备来完成我们的计时目标
这将如何运作?我们自定义的 observable 创建了一个流,它输出秒表上的数字,并由一个单独的流控制(这里称为control$)。因此,当control$ 发出“START”时,秒表启动,当它发出“STOP”时,秒表停止,当它发出“RESET”时,秒表将计数器设置回零。当control$ 出错或完成时,秒表出错或完成。
用 switchMap 和 Timer 实现
function createStopwatch(control$: Observable<string>, interval = 1000): Observable<number>{
return defer(() => {
let toggle: boolean = false;
let count: number = 0;
const endTicker$ = new Subject();
const ticker = () => {
return timer(0, interval).pipe(
takeUntil(endTicker$),
map(x => count++)
)
}
return control$.pipe(
tap({
next: _ => {/*Do nothing*/},
complete: () => {
endTicker$.next();
endTicker$.complete();
},
error: err => {
endTicker$.next();
endTicker$.complete();
}
}),
filter(control =>
control === "START" ||
control === "STOP" ||
control === "RESET"
),
switchMap(control => {
if(control === "START" && !toggle){
toggle = true;
return ticker();
}else if(control === "STOP" && toggle){
toggle = false;
return EMPTY;
}else if(control === "RESET"){
count = 0;
if(toggle){
return ticker();
}
}
return EMPTY;
})
);
});
}
用 setTimeout 实现
function createStopwatch(control: Observable<string>, interval = 1000): Observable<number> {
return new Observable(observer => {
let count: number = 0;
let tickerId: number = null;
const clearTicker = () => {
if(tickerId != null){
clearTimeout(tickerId);
tickerId = null;
}
}
const setTicker = () => {
const recursiveTicker = () => {
tickerId = setTimeout(() => {
observer.next(count++);
recursiveTicker();
}, interval);
}
clearTicker();
observer.next(count++);
recursiveTicker();
}
control.subscribe({
next: input => {
if(input === "START" && tickerId == null){
setTicker();
}else if(input === "STOP"){
clearTicker();
}else if(input === "RESET"){
count = 0;
if(tickerId != null){
setTicker();
}
}
},
complete: () => {
clearTicker();
observer.complete();
},
error: err => {
clearTicker();
observer.error(err);
}
});
return {unsubscribe: () => clearTicker()};
});
}
使用中的秒表
这是一个使用这个 observable 的例子。我通过主题管理控制流,但它可以很容易地被合并/映射 DOM 事件或类似的东西。
const control$ = new Subject<string>();
createStopwatch(control$, 250).subscribe(console.log);
// We send a new action to our control stream every 1 second
const actions = ["START", "STOP", "START", "RESET", "START"]
zip(from(actions), interval(1000)).pipe(
map((x,y) => x),
finalize(() => {
// After 5 seconds, unsubscribe via the control
// If our control finishes in any way (
// completes, errors, or is unsubscribed), our
// sopwatch reacts by doing the same.
control$.complete();
})
).subscribe(x => control$.next(x));
使用中的秒表 #2
这用setTimeout而不是interval来控制秒表。
const control$ = new Subject<string>();
createStopwatch(control$, 250).subscribe(console.log);
// We send a new action to our control stream every 1 second
const actions = ["START", "STOP", "START", "RESET", "START"]
actions.forEach((val, index) => {
setTimeout(() => {
control$.next(val);
},
index * 1000);
})
// Unsubscribe via the control
setTimeout(() => {
control$.complete();
}, actions.length * 1000);