Restart the timer on an rxjs interval

你。 提交于 2019-12-11 03:54:33

问题


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

export class RepeatingServiceCall<T> {
  private paused = false;
  private observable: Observable<T>;
  constructor(serviceCall: () => Observable<T>, delay: number) {
    this.observable = interval(delay).pipe(flatMap(() => (!this.paused ? serviceCall() : NEVER)));
  }
  setPaused(paused: boolean) {
    this.paused = paused;
  }
  getObservable() {
    return observable;
  }
}

This seems to work fine but the problem I am trying to solve is that I want the timer to reset when unpaused. So let's say that the interval time is 10 seconds and 5 seconds after the last time the interval emitted, setPaused(false) is called. In that scenario, I want it to emit immediately and then restart the timer. Would something like that be an easy thing to add?


回答1:


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.




回答2:


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




回答3:


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>



回答4:


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 :)




回答5:


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>


来源:https://stackoverflow.com/questions/55716687/restart-the-timer-on-an-rxjs-interval

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!