简体   繁体   中英

Pause an interval rxjs

I have a simple stopwatch, with using rxjs

Problem is: can't get how to pause a stream of my interval, and then continue it stackbiz

Take a look at my solution in this stackblitz

The component has to subscribe to the StopWatch-Service. I do not like the idea of giving a service a value and then the service CHANGES the value implicitly. Therefor i work with an explicit way to get the updated stopWatch (as an observable).

I work with "timer" instead of "interval", because "interval" would emit the first value (a 0) after a second, therefor my stopWath would have a delay,

There is a little trick. There is a private variable "timer$", a BehaviorSubject. And as soon as the counter is started, i start a timer, and subscribe to it. In that subscription each emit of the timer, will emit a new value for the BehaviorSubject.

Now i also store the subscription.
If i want to stop everything, i just unsubscribe from the "timer". As a result the clock stops. But because it is kind of decoupled from the behaviorSubject timer$ that one will still have stored the last value.

Or to say it differently:
The BehaviorSubject lives for ever, the consumer is never unsubscribed (only if he does it himself). And i kind of "attach" and "unattach" the timer function to it, whenever the counting should start or stop.

By the way, its a good habit to always make sure that your component will unsubscribe from all running observables, when the component is destroyed. If that is not done, than those still active subscriptions may result in performance or even worse problems in an application.

i hope it helps a bit

warm regards

I've seen stopwatch questions come up often enough that I figured it would be interesting to create a custom stopWatch observable. The RxJS way would be to implement this by switching into and out of timers/intervals.

Another interesting way to implement this is by using setTimeout instead. setTimeout should actually require a bit less memory as we're not leaning on the observable apparatus to accomplish our timing goals

How will this work? Our custom observable creates a stream that outputs the number on the stopwatch and is controlled by a separate stream (Here called control$ ). So when control$ emits "START", the stopWatch starts, when it emits "STOP", the stopwatch stops, and when it emits "RESET" the stopwatch sets the counter back to zero. When control$ errors or completes, the stopwatch errors or completes.

Implemented with switchMap and 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;
      })
    );
  });
}

Implemented with 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 in Use

Here is an example of this observable being used. I manage the control stream via a subject, but it could just as easily be merged/mapped DOM events or somesuch.

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 Use # 2

This controls the stopwatch with setTimeout instead of 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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM