skip to Main Content

In this version of my code the timer instantly jumps to 30 seconds.

timer();

function timer() {

  while (end == 0 && sec<30) {
    milsec++;
      if (milsec==100) {
        sec++;
        milsec = 0;
      }
      setTimeout(timer(), 10);
  }

}

I tried many things before. I got the error call stack size exceeded, many times the whole page just stopped loading. Now I have come to a point where the whole thing works at least halfway, but the delay of 10 milliseconds is not working. Probably there is a simple solution to this problem but I just coudn’t figure it out…

3

Answers


  1. Here’s one way you can implement a stopwatch, using Date.now() to get the number of milliseconds that have elapsed since the UNIX epoch. You can use the difference between that number and the current time, and compare that against the durationSecs to get the remaining time. Once there’s no time remaining, stop looping. This uses requestAnimationFrame to call the function approximately once every time the screen refreshes (so ~60 times per second on a standard 60 Hz monitor) because I don’t think there’s a reason to do it more often than that (especially for updating an element, which will only actually be updated once per refresh anyways). This isn’t a recursive call in the sense that it will overflow the call stack, but it’s technically recursive in the fact that it calls itself, but calling yourself inside of a requestAnimationFrame is a standard way to do updates once every frame.

    const timerElement = document.querySelector("#timer");
    timer(30);
    
    function timer(durationSecs, startingTimeMs) {
      startingTimeMs ??= Date.now(); // set the startingTimeMs if it's not defined
      const currentTimeMs = Date.now();
      const remainingMs = Math.max(durationSecs - (currentTimeMs - startingTimeMs) / 1000, 0);
      console.log(`Time remaining: ${remainingMs.toFixed(3)}`);
      timerElement.innerText = `Time remaining: ${remainingMs.toFixed(3)}`;
      if (remainingMs > 0) {
        requestAnimationFrame(() => timer(durationSecs, startingTimeMs));
      }
    }
    .as-console-wrapper { max-height: 20% !important; }
    <h1 id="timer"></h1>
    Login or Signup to reply.
  2. Remember to read up on functions, because setTimeout schedules some code to run at least as many milliseconds into the future as indicated, but only at least that many milliseconds into the future. The only guarantee it makes it that your code will not run for that many milliseconds, but it says nothing about when it’ll actually run. Could be 10ms, could be 2 minutes. Both are correct bahaviour.

    So you can’t use it as a clock, or as a stable counter for the passage of time. If you want that, you’ll need to actually use the clock, i.e. Date.now() or performance.now().

    Thankfully, writing a stopwatch doesn’t need an interval counter: all you care about is "the time when the user clicked start", and "the time the user clicked stop". Showing "a time spinner" isn’t remotely required for those two events to do what we need them to do, it’s just eye candy, so don’t do more work than is necessary to make something that looks like a number spinner.

    Pick an interval that is just about meaningful to humans, like 123ms (not 10, that’s way too fast) and then schedule an interval (rather than a timeout: we don’t care if there’s interval drift or hitches etc, the page update just needs to happen "fast enough". Beyond that, we don’t care). Every time that interval triggers, you check "how much time has passed since we started" by looking at the current time, and subtracting the start time, then you update the page with that value.

    Don’t increment anything during your interval, you already have all the information you need to show the passage of time.

    And crucially: by doing things this way it doesn’t matter what interval we update at because the only real times that matter are the start and stop times, the page update is purely cosmetic and has nothing to do with the actual time we’re interested in. In fact, even if the page update never happens, our stop watch will still be correct because we’re checking the real times only when the user clicks our button:

    let startTime = 0;
    let timer = undefined;
    
    const text = document.querySelector(`span`);
    const btn = document.querySelector(`button`)
    btn.addEventListener(`click`, toggle);
    
    function toggle() {
      if (startTime === 0) start();
      else stop();
    }
    
    function start() {
      // take note of the current time
      startTime = Date.now();
      // and start our "eye candy" page update at an interval that's
      // low enough to "look like a number spinner" without running
      // more than a purely cosmetic update needs to run.
      timer = setInterval(updatePage, 123);
      btn.textContent = `stop`;
    }
    
    function stop() {
      // let's see for how long we were running:
      console.log(`stopwatch ran for ${Date.now() - startTime}ms`);
      // then clear our time variable
      startTime = 0;
      // and stop our page update timer. Always remember to stop your timers.
      clearInterval(timer);
      btn.textContent = `start`;
    }
    
    function updatePage() {
      // how many seconds have passed since the user pressed start?
      const timePassed = ((Date.now() - startTime)/1000).toFixed(3);
      // cool, cool: update the page with that number
      text.textContent = `${timePassed}s`;
    }
    <span>0.000s</span> <button>start</button>
    Login or Signup to reply.
  3. Oh, there’s some things to clarify about this code…

    I’m not sure if you are trying to learn JavaScript and programming in general, but I’ll try to explain as simple as possible.

    First of all, JavaScript is a language based on events. In JavaScript, avoid code that can take a long time to run, because if you don’t, that may freeze your web page and your user can think that your web page is slow and buggy.

    Trying to use a while loop to wait for a timer would keep the page unresponsive while the timer is running.

    So, in JavaScript you should think that in another way…
    Do the thing you want, then tell the browser to call back your code.

    So, if you want to do something only after 30 seconds, your code can be:

    //First, declare a function that we want our browser to call after some time.
    function doSomethingWhenFinished() {
        //Your code goes here... For example, let's show a simple message.
        alert("Timeout finished!")
    }
    
    //Tell our browser that after 30s we want it to call the function "doSomethingWhenFinished"
    //If we did "doSomethingWhenFinished()", we would call our function instead of telling the browser we want it to be called later.
    setTimeout (doSomethingWhenFinished, 30000);
    

    But, if you want to do something each 10 milliseconds, you can do this (adapting from your original code):

    //First, declare the variables "sec" and "milsec" before using them.
    var sec = 0;
    var milsec = 0;
    
    function doSomethingEach10Millisseconds() {
        //Your code goes here, like updating something in your page.
    }
    
    function timer() {
        milsec++;
        if (milsec == 100) {
            sec++;
            milsec = 0;
        }
        if (sec < 30)
            setTimeout(timer, 10);
        else {
            doSomethingWhenFinished(); //Consider the same method as we declared before.
        }
    }
    
    timer()
    

    Maybe you were trying to repeat an procedural code. Take this C code that may represent the thing you’re trying to do:

    int sec = 0;
    int milsec = 0;
    
    while (sec < 30) {
        milsec++;
        if (milsec == 100) {
            sec++;
            console.log(sec);
            milsec = 0;
        }
        usleep(10000); //this function takes microsseconds. So 10 milliseconds = 10.000 microsseconds.
    }
    

    I did not test this C code.

    C is a procedural language, that is, the code runs a line, when finished goes to the next one.

    Hope that helps!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search