setInterval vs Observable's timer method, whic

2020-03-03 09:05发布

问题:

I have to make a timer in Angular, currently i'm doing it by Observable as follows:

  timer: Observable<any> = Observable.timer(0, 990);
  timerSubscription: Subscription;

  this.timerSubscription = this.timer.subscribe((value) => {
    this.tickerFunc();
  }); // inside ngOnInit

  tickerFunc () {
    this.timeNow++;

    this.timeNowInMinutes = Math.floor(this.timeNow / 60);

    if (this.timeNow >= this.TimeLimit) {
      this.finishtest();
    }
  }

So i have two Questions here:

1) Is Using Observable's timer is good, or should i use setInterval() method, or both has the same performance?

2) As you can see, for each time the timer ticks, i have to do two calculations:

first: i have to convert the seconds to minutes.

Second: I have to check if the timer has reached the time limit, and if the case, to unsubscribe the timer.

Both of my questions are related to make a timer as accurate as possible. (as close accurate to real-time as possible). And as you can notice, i have given the tim interval of 990 milliseconds instead of 1000 milliseconds to cover the time loss occured because of the calculations inside the tickerFunc() body.

回答1:

Strictly for executing a function after a given interval an Observable might be a bit overkill, but since you need to manipulate that interval it should be a good use case:

import { timer } from 'rxjs/observable/timer';
import { take } from 'rxjs/operators';

timer(0, 990).pipe(
    take(this.timeLimit),
)
.subscribe((v) => this.timeNowInMinutes = Math.floor(v / 60),
           (err) => console.log(err),
           () => this.finishTest());

Notice I've used the take operator to only have the required number of events and call finishTest in the completed callback.

I've used the new lettable operators available from RxJs 5.5, code will be quite similar with the old version.

You can also remove the subscription and use the async pipe in Angular, but in this case the completion call to finishTest is a bit hidden away:

const $timerStream = timer(0, 990).pipe(
    take(this.timeLimit),
    map(v => Math.floor(v + 60 / 60)),
    tap(v => v === this.timeLimit ? this.finishTest() : undefined),
);

You can use map to transform the values to what you need (notice I've added a + 60 there to make sure they are increasing, you can add whatever initial value you had in timeNow) and you use the async pipe in the template to render this value. The tap method (previously known as do) will check if it's the last item emitted and call finishTest if it is.