Unterbrechen Sie ein Intervall rxjs
Ich habe eine einfache Stoppuhr mit rxjs
Das Problem ist: Ich kann nicht verstehen , wie ich einen Stream meines Intervalls anhalten und dann das Stackbiz fortsetzen kann
Antworten
Ich habe oft genug Fragen zu Stoppuhren gesehen, so dass ich dachte, es wäre interessant, eine benutzerdefinierte Stoppuhr zu erstellen, die beobachtet werden kann. Der RxJS- Weg wäre, dies durch Umschalten in und aus Timern / Intervallen zu implementieren.
Eine andere interessante Möglichkeit, dies zu implementieren, ist die Verwendung von setTimeout. setTimeout sollte eigentlich etwas weniger Speicher benötigen, da wir uns nicht auf den beobachtbaren Apparat stützen, um unsere Timing-Ziele zu erreichen
Wie wird das funktionieren? Unser benutzerdefiniertes Observable erstellt einen Stream, der die Nummer auf der Stoppuhr ausgibt und von einem separaten Stream (hier genannt control$) gesteuert wird . Wenn also control$"START" ausgegeben wird, startet die Stoppuhr, wenn sie "STOP" ausgibt, stoppt die Stoppuhr und wenn sie "RESET" ausgibt, setzt die Stoppuhr den Zähler zurück auf Null. Wenn control$Fehler auftreten oder abgeschlossen sind, ist die Stoppuhr fehlerhaft oder abgeschlossen.
Implementiert mit switchMap und 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;
})
);
});
}
Implementiert mit 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()};
});
}
StopWatch im Einsatz
Hier ist ein Beispiel für die Verwendung dieses Observablen. Ich verwalte den Kontrollstrom über ein Thema, aber es können genauso gut DOM-Ereignisse oder ähnliches zusammengeführt / zugeordnet werden.
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));
StopWatch in Verwendung # 2
Dies steuert die Stoppuhr mit setTimeoutstatt 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);
Schauen Sie sich meine Lösung in diesem Stackblitz an
Die Komponente muss den StopWatch-Service abonnieren. Ich mag die Idee nicht, einem Service einen Wert zu geben, und dann ändert der Service den Wert implizit. Daher arbeite ich mit einer expliziten Methode, um die aktualisierte stopWatch (als Observable) zu erhalten.
Ich arbeite mit "Timer" anstelle von "Intervall", da "Intervall" nach einer Sekunde den ersten Wert (eine 0) ausgeben würde, daher hätte mein stopWath eine Verzögerung.
Es gibt einen kleinen Trick. Es gibt eine private Variable "timer $", ein BehaviorSubject. Und sobald der Zähler gestartet ist, starte ich einen Timer und abonniere ihn. In diesem Abonnement gibt jede Ausgabe des Timers einen neuen Wert für das BehaviorSubject aus.
Jetzt speichere ich auch das Abonnement.
Wenn ich alles stoppen möchte, melde ich mich einfach vom "Timer" ab. Infolgedessen stoppt die Uhr. Aber weil es irgendwie vom behaviourSubject entkoppelt ist timer$, hat man noch den letzten Wert gespeichert.
Oder anders
ausgedrückt : Das BehaviorSubject lebt für immer, der Verbraucher wird niemals abgemeldet (nur wenn er es selbst tut). Und ich "hänge" und "hebe" die Timer-Funktion daran auf, wann immer die Zählung beginnen oder stoppen soll.
Übrigens ist es eine gute Angewohnheit, immer sicherzustellen, dass sich Ihre Komponente von allen laufenden Observablen abmeldet, wenn die Komponente zerstört wird. Wenn dies nicht getan wird, können diese noch aktiven Abonnements zu Leistung oder noch schlimmeren Problemen in einer Anwendung führen.
Ich hoffe es hilft ein bisschen
herzliche Grüße