How can I make 2 counters running back to back with javascript?

Viewed 54

The following function naturally enters the same loop over and over again. What I want to do is start counting down from 25 seconds, when it's finished, start counting down from 10 seconds, then go back to 25 seconds. But because of the condition I wrote in the else part, it always counts backwards from 10 seconds. How can I fix this?

        var interval = 25000;
        var interval1 = 10000;

        function millisToMinutesAndSeconds(millis) {
            var seconds = ((millis % 60000) / 1000).toFixed(0);
            return (seconds < 10 ? "0" : "") + seconds;
        }
        function tensecond() {
            localStorage.endTime = +new Date() + interval1;
        }

        function reset() {
            localStorage.endTime = +new Date() + interval;
        }

        setInterval(function () {
            var remaining = localStorage.endTime - new Date();
            if (remaining >= 0) {
                document.getElementById("timer").innerText =
                    millisToMinutesAndSeconds(remaining);
            } else {
                tensecond();
            }
        }, 100);
3 Answers

Some comments:

  • Don't use the localStorage object to store your own properties. This has nothing to do with the purpose of localStorage. Just use a global variable (if you need local storage, then use its getItem and setItem methods)

  • Don't use toFixed(0) to round a number to an integer. Moreover, the comparison of that string with 10 will make a character-based comparison, not a numerical comparison. Instead use Math.round, or more appropriate here: Math.floor.

  • Don't use new Date() when you want a number of milliseconds instead of a Date object. Use Date.now() instead.

  • Don't do arithmetic on values that are not initialised. Initialise endTime before starting any logic on it. So call reset() before calling setInterval()

As to your question:

One way to get this to work is to make a cycle that covers both intervals added together. Then at each tick check whether the remaining time falls inside the first or second interval. Adjust the displayed remaining time accordingly.

Here is how that looks:

var interval = 25000;
var interval1 = 10000;
var endTime;

function millisToMinutesAndSeconds(millis) {
    // Use floor instead of toFixed
    var seconds = Math.floor((millis % 60000) / 1000);
    return (seconds < 10 ? "0" : "") + seconds;
}

function reset() {
    // Use Date.now() instead of +new Date()
    // And create a cycle length that covers both intervals
    endTime = Date.now() + interval + interval1;
}

reset();
setInterval(function () {
    var remaining = endTime - Date.now();
    if (remaining >= 0) {
        // Adjust the time to display 
        // depending on where in the total interval we are:
        if (remaining >= interval1) remaining -= interval1;
        document.getElementById("timer").innerText =
            millisToMinutesAndSeconds(remaining);
    } else {
        reset()
    }
}, 100);
<div id="timer"></div>

These are the facts:

  • The first time the (anonymous) interval function runs, localStorage.endTime isn't initialized, so has value undefined.
  • Any arithmetic operations on undefined result in NaN1, 2, 3, so remaining is initialized to NaN.
  • Any comparisons to NaN (other than != and !==) are false4, 5, 6, so the first time the interval function runs, it calls tensecond.
  • Thereafter, the interval function counts down. When the timer runs out, it again calls tensecond.

Short version: reset is never called.

ECMAScript, 13th Ed references

  1. § 13.15.3 ApplyStringOrNumericBinaryOperator
  2. § 7.1.4 ToNumber
  3. § 6.1.6.1.7 Number::add ( x, y )
  4. § 13.11.1 Runtime Semantics: Evaluation
  5. § 7.2.15 IsLooselyEqual ( x, y )
  6. 6.1.6.1.13 Number::equal

There's no need to incorporate specific datetimes or local storage if you just need an alternating countdown timer. A simpler technique is to just keep track of the number of remaining seconds and do updates after a repeated 1s delay, subtracting a second from the total each time.

Here's an example of that (and it also displays each second rounded up instead of rounded down — so it starts with 25 (or 10) and resets at the exact moment that 0 is reached rather than displaying 0 for an entire second):

const timerElement = document.getElementById('timer');

function updateTimerElement (seconds) {
  timerElement.textContent = String(seconds).padStart(2, '0');
}

function delay (ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function countdown (seconds) {
  while (seconds > 0) {
    updateTimerElement(seconds);
    await delay(1e3); // 1e3 is 1000 (1s)
    seconds -= 1;
  }
  // You might want to update the timer one final time in order to show 0
  // if you ever stop looping the countdowns:
  // updateTimerElement(seconds);
}

async function main () {
  // Store the total number of seconds for each countdown in order:
  const secondsList = [25, 10];
  // Keep track of the current one:
  let listIndex = 0;

  while (true) {
    // Get the current number of seconds from the list:
    const seconds = secondsList[listIndex];
    // Run the countdown timer:
    await countdown(seconds);
    // Update the index to the next number of seconds in the list:
    listIndex = (listIndex + 1) % secondsList.length;
  }
}

main();
body { font-family: sans-serif; font-size: 4rem; }
<div id="timer"></div>

Finally, take care to note that JavaScript timers are not precise timing tools. See more info at: Reasons for delays longer than specified - setTimeout() - Web APIs | MDN

Related