1

I have a "tabata" timer. So far it counts down through a get ready phase, then a work phase and finally a rest phase. After the rest phase, I would like to re-loop through the work and rest phases several times.

However, at the end of the rest phase, the call to the work phase function does not work.

I get the following errors in the console.log when using the code below...

ERROR TypeError: Unable to set property 'intervalOne' of undefined or null reference

ERROR TypeError: Unable to get property 'restTime' of undefined or null reference

I am using the setInterval the same way so I don't understand why this time it won't work.

Why isn't my code looping through?

I've put the seperate funtions in different places throughout the code, I taken away the setIntervals to get rid of the error, but of course that stops the counter counting down! I'm at a loss.

   repeat(event) {
   this.intervalOne = setInterval(() => this.begin(this.countdown, this.tabata), 1000)
  }

  begin(myCountdown, myTabata) {
    let first = document.querySelector('#counter');
    this.set = this.set;
    if (this.set == 1) {
      this.countdown = myCountdown - 1;
      this.countdown > 0
      if (this.countdown < 10) {
        this.countdown = "0" + this.countdown;
      }
      first.innerHTML = this.countdown;
      if (this.countdown == 0) {
        clearInterval(this.intervalOne);
        this.intervalTwo = setInterval(() => work(), 1000)
        first.classList.remove('orange');
      }
      let intervalTwo = this.intervalTwo;
      let secondCount: any = +myTabata.split(':')[1];

      function work() {
        if (secondCount < 10) {
          secondCount = "0" + secondCount;
        }
        secondCount = secondCount - 1;
        secondCount > 0;
        first.innerHTML = secondCount;
        if (secondCount == 0) {
          clearInterval(intervalTwo);
          first.classList.remove('green');
          first.classList.remove('orange');
          secondCount = +myTabata.split(':')[1];
          this.intervalOne = setInterval(() => rest(), 1000)
          console.log(secondCount);
        }
      }
      let interval = this.intervalOne;
      let myRest = this.restTime;
      function rest() {
        if (myRest < 10) {
          myRest = "0" + myRest;
        }
        myRest = myRest - 1;
        myRest > 0;
        first.innerHTML = myRest;
        if (myRest == 0) {
          clearInterval(interval);
          myRest = this.restTime;
          this.intervalTwo = setInterval(() => work(), 1000)
        }
      }
    }
  }
JoshG
  • 6,472
  • 2
  • 38
  • 61
jesusWalks
  • 355
  • 4
  • 16

1 Answers1

1

Simply, do not use setInterval, and furthermore do not ask the DOM in loops.

You are using querySelector each loop. , When reading the DOM, the browser is parsing the whole document to get the asked value.

This is okay in punctual -one-time- situation, but in loops, this is heavily slowing your interval. When a loop is taking more time than the interval, the browser will simply extend the interval loop, to continue the rendering. This is where issues are coming. After minutes like so, this can even freeze the tab and the whole computer.

We now can use requestAnimationFrame for fast loops.

Using the Date object, to compare the start Date and the current. This is way faster than asking the DOM.

Here is an example,this is pretty close to what you are asking. You should be able to modify it as your precise need.

/* Seconds to (STRING)HH:MM:SS.MS -----------------------*/
function secToTimer(sec){
  let o = new Date(0)
  let p =  new Date(sec * 1000)  
  return new Date(p.getTime()-o.getTime()).toString().split(" ")[4] + "." + p.getMilliseconds()
}


let job, origin = new Date().getTime()
const timer = () => {
  job = requestAnimationFrame(timer)
  OUT.textContent = secToTimer((new Date().getTime() - origin) / 1000)
}

requestAnimationFrame(timer)
span {font-size:4rem}
<span id="OUT"></span>
<br>
<button onclick="origin = new Date().getTime()">RESET</button>
<button onclick="requestAnimationFrame(timer)">RESTART</button>
<button onclick="cancelAnimationFrame(job)">STOP</button>

Source
CanIuse #requestAnimationFrame

NVRM
  • 11,480
  • 1
  • 88
  • 87
  • 1
    Thanks for the well-thought out answer. However, you've just blown my mind, I have no idea how to impliment this into my code. I'm going to search YouTube for some more examples of the reqquestAnimationFrame() function... Cheers. – jesusWalks Aug 25 '19 at 14:00
  • Yes this is is bit disturbing :), no worries it's just a function with a callback, here on this case it's calling himself on the callback, which create the loop. The browser is then looping at the frame rate of your video card/screen, usually 60FPS. This is the way to go for sure on the future! – NVRM Aug 25 '19 at 14:12