[solved] Pomodoro Clock bug - countdown double activation

I’m working on the Pomodoro Clock project and have made a lot of progress so far.

The clock is functioning correctly for all of the key requirements (you can set the session/break length, pause execution and come back to where you left off, reset, and it gives you a notification sound when the session is over).

But there’s a bug in my program.

When the start/stop button is pressed several times in a quick succession, the countdown function is triggering twice (causing the clock to countdown two seconds for every second passed). You can replicate the bug with the demo here – just click the start/stop button a few times quickly to see what I mean.

I tried wrapping the entire function in an IIFE with an if statement checking whether the function should execute (based on a bool variable that confirms if the timer should be active), but that didn’t change the functionality.

Any advice appriciated! I’ve been sitting looking at the code for ages now and can’t see any way to make it work.

The live demo is here.
The complete code is here.


Below is the countdown timer function on its own.

// Methods for the countdown timer.
var timerController = {
    timerActiveSwitch: function() {
        model.timerActive = !model.timerActive;
        
        if (elements.countdownToggle.innerHTML === "<p>Start</p>") {
            elements.countdownToggle.className = "stopBtn";
            elements.countdownToggle.innerHTML = "<p>Stop</p>";
        } else {
            elements.countdownToggle.className = "startBtn";
            elements.countdownToggle.innerHTML = "<p>Start</p>";
        }
    },
    reset: function() {
        model.timerActive = false;
        model.countdownStep = "session";
        
        model.breakInt = 5;
        model.sessionInt = 25;
        
        userToggle.timeNull();
        
        elements.timerTitle.innerHTML = "Session";
        elements.breakIntDOM.innerHTML = model.breakInt;
        elements.sessionIntDOM.innerHTML = model.sessionInt;
        elements.timer.innerHTML = model.sessionInt;
        elements.countdownToggle.className = "startBtn";
        elements.countdownToggle.innerHTML = "<p>Start</p>";
    },
    stepChange: function() {
        model.countdownStep = "break";
        elements.timerTitle.innerHTML = "Break";
        
        var audio = new Audio();
        audio.src = "assets/notification.mp3"
        
        audio.play();
    },
    countdownTimerActivator: function() {
        this.timerActiveSwitch();
        
        if (model.countdownStep === "session") {
            if (model.mins === null || model.seconds === null) {
                model.mins = model.sessionInt;
                model.seconds = 0;
            }
        } else if (model.countdownStep === "break") {
            if (model.mins === null || model.seconds === null) {
                model.mins = model.breakInt;
                model.seconds = 0;
            }
        }
        
        var timer = setInterval(timeUpdate, 1000);

        function clearTimer() {
            clearTimeout(timer);
        }
        
        // Does the actual countdown each second.
        function timeUpdate() {
            if (!model.timerActive) {
                clearTimer();
            } else if (model.mins === 0 && model.seconds === 0) {
                clearTimer();
                model.mins = null;
                model.seconds = null;
            } else if (model.seconds === 0) {
                model.mins--;
                model.seconds = 59; 
            } else {
                model.seconds--;
            }
            
            if (model.countdownStep === "session") {
                if (model.timerActive && model.mins !== null && model.seconds !== null) {
                    elements.timer.innerHTML = model.mins + ":" + model.seconds;   
                } else if (model.timerActive && model.mins === null && model.seconds === null) {
                    clearTimer();
                    timerController.stepChange();
                    timerController.timerActiveSwitch();
                    timerController.countdownTimerActivator();
                }
            } else if (model.countdownStep === "break") {
                if (model.timerActive && model.mins !== null && model.seconds !== null) {
                    elements.timer.innerHTML = model.mins + ":" + model.seconds;   
                } else if (model.timerActive && model.mins === null && model.seconds === null) {
                    clearTimer();
                    timerController.reset();
                    return;
                }
            }
        }
    },
};

Update
I managed to resolve the issue. The code that fixed my problem:

    function startTimer() {
        clearInterval(timer);
        timer = setInterval(timeUpdate, 1000);
    }
    startTimer();

I’m actually a little ashamed it took me this long to see the problem.

Having the timer = setInterval(timeUpdate, 1000); variable just inside the main countdownTimerActivator was the issue. If the start/stop button was clicked a few times quickly, it was creating several intervals (causing a faster countdown). The code works by clearing any existing intervals every time an interval is created.

It seems to have been a scoping issue (that’s why the if statement checks reccomended weren’t working – if statements don’t have their own scope so the timer variable was still scoped to the countdownTimerActivator function).

1 Like

i also had this problem and below is how i fixed it … thing is i had two separate buttons one for start one for pause …
i created a variable timerRunning set to false;
and when start button was pressed it would check to see if it was false and check sessionTime was not equal to ‘0’ … if those conditions were met it would start timer…
now if someone clicked start again i would check timerRunning which would now be true and sessionTime … but as timerRunning is false … if would fail and it would exit without calling setInterval again and only setInterval that was running would be active.

in the pauseTimers if someone presses the pause button i reset the timerRunning to false so when i press start button i can restart timer

Im stuck for time now but will try and see is it possible for you to do something … it could be you have to wrap a if statement around line 137 var timer = setInterval(timeUpdate, 1000);

startTimers(){
       debugger;
       let self = this;
       if(this.timerRunning === false && this.sessionTime != '0') {
       this.start = setInterval(function(){self.updateTime();},1000);
       this.timerRunning = true;
       }
     },
     pauseTimers(){
       if(this.timerRunning === true) {
       clearInterval(this.start);
       this.timerRunning = false;
       }
     },

well i had no luck … tried wrapping an if statement around your var timer = setInterval(timeUpdate, 1000); …
but that didnt sort it … not able to debug on quick click as it just picks up first click in debugger … line that i think leads to the problem is line 87 model.timerActive = !model.timerActive; … i tried wrapping an if statement around this and i stopped the extra setIntervals happening but affected the timer eg ran when stop pressed stop when start pressed.

As far i see you have following options … continue and hope you find a solution or someone is able to give you a solution
Or redesign … one redesign option would be to have two separate divs linked to start and pause … where you can toggle stop to be hidden onload and set visible when start clicked and start toggled to hide
and then look at your code and separate it out … eg when start clicked keep it real simple just have a call to setInterval which you wrap in a if statement to check if its allready running … and when stop clicked just have a call to clear setInterval … and these too bits of code are separate from the running of the count down code … at the moment you have all this mixed in a big section of code which is also calling other functions

Again this is just my suggestion … i can only advise on what i would do and have previously done eg i have wrote code before and close to finishing realized something was not working as i intended and had to restart rather than trying to hack in a solution

Thanks for all the help.

I tried wrapping an if statement around the timer variable as well, but I can see why this didn’t work.

I’m thinking about scrapping the code so far and restarting. It’s an absolute mess at the moment.

I got a the similar problem at this project. It turned out to be setInterval() function actually involves with multi-threads problem. It’s a complicate one. I hope you can solve your problem if you dig into multi-threads issues.

I managed to get it all working. It was a scoping issue.

Thanks for the help! Your comment nudged me in the right direction.

1 Like

Thank you.

I got it working by wrapping the interval variable in a function and clearing the variable before the interval is created.