Pausar un intervalo rxjs
Tengo un cronómetro simple, con rxjs
El problema es: no puedo saber cómo pausar una transmisión de mi intervalo y luego continuar con stackbiz
Respuestas
He visto surgir preguntas sobre el cronómetro con tanta frecuencia que pensé que sería interesante crear un cronómetro observable personalizado. La forma RxJS sería implementar esto cambiando dentro y fuera de temporizadores / intervalos.
Otra forma interesante de implementar esto es usando setTimeout en su lugar. setTimeout en realidad debería requerir un poco menos de memoria, ya que no nos apoyamos en el aparato observable para lograr nuestros objetivos de tiempo
¿Cómo funcionará esto? Nuestro observable personalizado crea un flujo que genera el número en el cronómetro y es controlado por un flujo separado (aquí llamado control$). Entonces, cuando control$emite "START", el cronómetro arranca, cuando emite "STOP", el cronómetro se detiene, y cuando emite "RESET", el cronómetro pone el contador a cero. Cuando se control$produce un error o se completa, el cronómetro falla o se completa.
Implementado con switchMap y 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;
})
);
});
}
Implementado con 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 en uso
A continuación se muestra un ejemplo del uso de este observable. Administro el flujo de control a través de un tema, pero podría fácilmente fusionarse / mapear eventos DOM o algo así.
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 en uso # 2
Esto controla el cronómetro con en setTimeoutlugar de 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);
Eche un vistazo a mi solución en este stackblitz
El componente debe suscribirse al servicio StopWatch. No me gusta la idea de darle un valor a un servicio y luego el servicio CAMBIA implícitamente el valor. Por lo tanto, trabajo con una forma explícita de obtener el stopWatch actualizado (como un observable).
Trabajo con "temporizador" en lugar de "intervalo", porque "intervalo" emitiría el primer valor (un 0) después de un segundo, por lo que mi stopWath tendría un retraso,
Hay un pequeño truco. Hay una variable privada "timer $", un BehaviorSubject. Y tan pronto como se inicia el contador, inicio un temporizador y me suscribo. En esa suscripción, cada emisión del temporizador emitirá un nuevo valor para BehaviorSubject.
Ahora también guardo la suscripción.
Si quiero detener todo, simplemente me doy de baja del "temporizador". Como resultado, el reloj se detiene. Pero debido a que está algo desacoplado del comportamiento, timer$uno todavía habrá almacenado el último valor.
O para decirlo de otra manera:
The BehaviorSubject vive para siempre, el consumidor nunca se da de baja (solo si lo hace él mismo). Y como que "adjunto" y "desengancho" la función del temporizador, cada vez que el conteo debe comenzar o detenerse.
Por cierto, es un buen hábito asegurarse siempre de que su componente se anulará la suscripción de todos los observables en ejecución cuando se destruya el componente. Si eso no se hace, las suscripciones aún activas pueden resultar en el rendimiento o incluso peores problemas en una aplicación.
espero que ayude un poco
un cordial saludo