Unterbrechen Sie ein Intervall rxjs

Nov 04 2020

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

1 MrkSef Nov 04 2020 at 16:02

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);
1 JanRecker Nov 04 2020 at 12:36

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