Restart the timer on an rxjs interval

后端 未结 5 1088
傲寒
傲寒 2020-12-19 04:31

I created a class which sets up a pausable rxjs observable on an interval:

export class RepeatingServiceCall {
  private paused = false;
  private o         


        
相关标签:
5条回答
  • 2020-12-19 04:37

    If you use timer instead of interval, and set the initial delay to 0, then your interval will fire immediately.

    You can use takeUntil operator to prevent the interval to run always, and repeatWhen operator to restart it whenever you want:

    import { Observable, Subject, timer } from 'rxjs';
    import { repeatWhen, switchMap, takeUntil } from 'rxjs/operators';
    
    export class RepeatingServiceCall<T> {
      readonly observable$: Observable<T>;
      private readonly _stop = new Subject<void>();
      private readonly _start = new Subject<void>();
    
      constructor(serviceCall: () => Observable<T>, delay: number) {
        this.observable$ = timer(0, delay)
          .pipe(
            switchMap(() => serviceCall()),
            takeUntil(this._stop),
            repeatWhen(() => this._start)
          );
      }
      start(): void {
        this._start.next();
      }
      stop(): void {
        this._stop.next();
      }
    }
    

    Here is a working StackBlitz example.

    P.S.: Getters and setters are working different in typescript. So you do not need classic getter concept, you can just make the attribute public and readonly.

    0 讨论(0)
  • 2020-12-19 04:44

    check this code

    /**
    * it is a simple timer created by via rxjs
    * @author KentWood
    * email minzojian@hotmail.com
    */
    function rxjs_timer(interval, times, tickerCallback, doneCallback, startDelay) {
        this.pause = function () {
            this.paused = true;
        }
        this.resume = function () {
            this.paused = false;
        }
        this.stop = function () {
            if (this.obs) {
                this.obs.complete();
                this.obs.unsubscribe();
            }
            this.obs = null;
    
        }
        this.start = function (interval, times, tickerCallback, doneCallback, startDelay) {
            this.startDelay = startDelay || 0;
            this.interval = interval || 1000;
            this.times = times || Number.MAX_VALUE;
            this.currentTime = 0;
            this.stop();
    
            rxjs.Observable.create((obs) => {
                this.obs = obs;
                let p = rxjs.timer(this.startDelay, this.interval).pipe(
                    rxjs.operators.filter(() => (!this.paused)), 
                  rxjs.operators.tap(() => {
                        if (this.currentTime++ >= this.times) {
                            this.stop();
                        }
                    }),
                  rxjs.operators.map(()=>(this.currentTime-1))
                );
                let sub = p.subscribe(val => obs.next(val), err => obs.error(err), () => obs
                    .complete());
                return sub;
            }).subscribe(tickerCallback, null, doneCallback);
        }
        this.start(interval, times, tickerCallback, doneCallback, startDelay);
    }
    /////////////test/////////////
    var mytimer = new rxjs_timer(
    1000/*interval*/, 
    10 /*times*/, 
    (v) => {logout(`time:${v}`)}/*tick callback*/, 
    () => {logout('done')}/*complete callback*/, 
    2000/*start delay*/);
    
    //call mytimer.pause() 
    //call mytimer.resume() 
    //call mytimer.stop() 
    
    
    
    
    function logout(str){
      document.getElementById('log').insertAdjacentHTML( 'afterbegin',`<p>${str}</p>`)
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.js"></script>
    
    <button onclick="mytimer.pause()"> pause</button>
    <button onclick="mytimer.resume()"> resume</button>
    <button onclick="mytimer.stop()"> stop</button>
    <div id='log'></div>

    0 讨论(0)
  • 2020-12-19 04:46

    Yet another approach with a switchMap:

    const { fromEvent, timer } = rxjs;
    const { takeUntil, switchMap, startWith } = rxjs.operators;
    
    const start$ = fromEvent(document.getElementById('start'), 'click');
    const stop$ = fromEvent(document.getElementById('stop'), 'click');
    
    
    start$.pipe(
      startWith(void 0), // trigger emission at launch
      switchMap(() => timer(0, 1000).pipe(
        takeUntil(stop$)
      ))
    ).subscribe(console.log);
    <script src="https://unpkg.com/rxjs@6.4.0/bundles/rxjs.umd.min.js"></script>
    
    <button id="start">start</button>
    <button id="stop">stop</button>

    And a simpler one, that merges start and stop Observables to switch off them:

    const { fromEvent, merge, timer, NEVER } = rxjs;
    const { distinctUntilChanged, switchMap, mapTo, startWith } = rxjs.operators;
    
    const start$ = fromEvent(document.getElementById('start'), 'click');
    const stop$ = fromEvent(document.getElementById('stop'), 'click');
    
    
    merge(
      start$.pipe(mapTo(true), startWith(true)),
      stop$.pipe(mapTo(false))
    ).pipe(
      distinctUntilChanged(),
      switchMap(paused => paused ? timer(0, 1000) : NEVER)
    )
    .subscribe(console.log);
    <script src="https://unpkg.com/rxjs@6.4.0/bundles/rxjs.umd.min.js"></script>
    
    <button id="start">start</button>
    <button id="stop">stop</button>

    And another, even wierder approach, using repeat() :

    const { fromEvent, timer } = rxjs;
    const { take, concatMap, takeUntil, repeat } = rxjs.operators;
    
    const start$ = fromEvent(document.getElementById('start'), 'click');
    const stop$ = fromEvent(document.getElementById('stop'), 'click');
    
    
    start$.pipe(
      take(1),
      concatMap(()=>timer(0, 1000)),
      takeUntil(stop$),
      repeat()
    ).subscribe(console.log);
    <script src="https://unpkg.com/rxjs@6.4.0/bundles/rxjs.umd.min.js"></script>
    
    <button id="start">start</button>
    <button id="stop">stop</button>

    Just wanted to join this party :)

    0 讨论(0)
  • 2020-12-19 04:49

    You can achieve the behavior you are describing with the following snippet:

    const delay = 1000;
    
    const playing = new BehaviorSubject(false);
    
    const observable = playing.pipe(
      switchMap(e => !!e ? interval(delay).pipe(startWith('start')) : never())
    );
    
    observable.subscribe(e => console.log(e));
    
    // play:
    playing.next(true);
    
    // pause:
    playing.next(false);
    
    • When the playing Observable emits true, the switchMap operator will return a new interval Observable.
    • Use the startWith operator to emit an event immediately when unpausing.
    • If you wish to have the interval start automatically when subscribing to the observable, then simply initialize the BehaviorSubject with true.

    StackBlitz Example

    0 讨论(0)
  • 2020-12-19 04:58

    You can abandon the old timer on start and start a new one on start.

    const { interval, Subject, fromEvent } = rxjs;
    const { takeUntil } = rxjs.operators;
    
    let timer$;
    
    const pause = new Subject();
    
    const obs$ = new Subject();
    
    obs$.subscribe(_ => { console.log('Timer fired') });
    
    function start() {
      timer$ = interval(1000);
      timer$.pipe(takeUntil(pause)).subscribe(_ => { obs$.next(); });
    }
    
    function stop() {
      pause.next();
      timer$ = undefined;
    }
    
    fromEvent(document.getElementById('toggle'), 'click').subscribe(() => {
      if (timer$) {
        stop();
      } else {
        start();
      }
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
    <button id="toggle">Start/Stop</button>

    0 讨论(0)
提交回复
热议问题