Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Timer function not working correctly when stopped and started multiple times

First of all, you can find an example of my code in JS Fiddle and also below the question.

I'm working on a personal training webapp and basically you can hit play and then you get five minutes to do a series of tasks in a random order. The program creates the sessionTasks array in which are put in a random order tasks for the tasks array in order to fit the five minute limit. Right now the tasks array is just one I created with four tasks and the respective times just for testing.

The problem I ran into is the following: When you click the task so you can move forward to the next task, the next time you play seconds will move faster. The way I found to replicate is:

  1. Click play.
  2. Power through the tasks by clicking the task text very quickly.
  3. Click play again.

Now the seconds should be moving quicker. If not, repeat what you just did. It is irregular but it usually does it in the second try.

I cannot for the life of me understand why it behaves like this. I thought that maybe it was creating more Timers that all used #taskTimer to run but that didn't make sense to me. Is it something wrong with the Timer function? What is wrong in my code?

mainMenu();


var totalSessionTasks, taskIterator, selectedTimeInSecs = 300;

var taskTimer = new Timer("#taskTimer", nextTask);

var globalTimer = new Timer("#globalTimer", function() {

});

var tasks = [
  ["First task", 0, 30],
  ["Second task", 0, 15],
  ["Third task", 0, 10],
  ["Fourth task", 3, 0]
];

var sessionTasks = [

]




function setUpSession() {

  sessionTasks = []

  if (tasks.length != 0) {

    var sessionTasksSeconds = 0; //the seconds of the session being filled
    var sessionTasksSecondsToFill = selectedTimeInSecs; //seconds left in the session to fill
    var newTaskSeconds = 0; //seconds of the next task being added to the session
    var sessionFull = false;

    console.log('Session Empty');

    while (sessionFull === false) {

      var areThereAnyTaskThatFitInTheSession =
        tasks.some(function(item) {
          return ((item[1] * 60 + item[2]) <= sessionTasksSecondsToFill) && (item != sessionTasks[sessionTasks.length - 1]);
        });
      console.log(areThereAnyTaskThatFitInTheSession);

      if (areThereAnyTaskThatFitInTheSession) {
        do {
          var randTaskNum = Math.floor(Math.random() * tasks.length);
        } while (((tasks[randTaskNum][1] * 60 + tasks[randTaskNum][2]) > sessionTasksSecondsToFill) || (tasks[randTaskNum] == sessionTasks[sessionTasks.length - 1]))

        sessionTasks.push(tasks[randTaskNum]);
        newTaskSeconds = (tasks[randTaskNum][1]) * 60 + tasks[randTaskNum][2];
        sessionTasksSecondsToFill -= newTaskSeconds;
        sessionTasksSeconds += newTaskSeconds;

        console.log(tasks[randTaskNum][0] + ": " + newTaskSeconds + "s");
        console.log(sessionTasksSeconds)

      } else if (sessionTasks.length == 0) {
        note("All your tasks are too big for a game of " + selectedTimeInSecs / 60 + " minutes!");
        break;
      } else {
        console.log('Session full');
        sessionFull = true;
        taskIterator = -1;
        totalSessionTasks = sessionTasks.length;
        console.log(totalSessionTasks);

        globalTimer.set(0, sessionTasksSeconds);
        nextTask();
        globalTimer.run();
        taskTimer.run();
      }


    }

  } else {
    note("You don't have have any tasks in your playlists!");
  }

}


function nextTask() {

  if (taskIterator + 1 < totalSessionTasks) {
    taskIterator++;
    $("#taskText").text(sessionTasks[taskIterator][0]);
    globalTimer.subtract(0, taskTimer.getTotalTimeInSeconds())
    taskTimer.set(sessionTasks[taskIterator][1], sessionTasks[taskIterator][2]);
    $("#taskCounter").text(taskIterator + 1 + " of " + totalSessionTasks + " tasks");
  } else {
    mainMenu();
    taskTimer.stop();
    globalTimer.stop();
    note("Thanks for playing!");
  }


}

//timer object function
function Timer(element, callback) {

  var ac, minutes, seconds, finalTimeInSeconds, displayMinutes, displaySeconds, interval = 1000,
    self = this,
    timeLeftToNextSecond = 1000;
  this.running = false;

  this.set = function(inputMinutes, inputSeconds) {

    finalTimeInSeconds = inputMinutes * 60 + inputSeconds;
    minutes = (Math.floor(finalTimeInSeconds / 60));
    seconds = finalTimeInSeconds % 60;

    this.print();
  }

  this.add = function(inputMinutes, inputSeconds) {

    finalTimeInSeconds += inputMinutes * 60 + inputSeconds;
    finalTimeInSeconds = (finalTimeInSeconds < 0) ? 0 : finalTimeInSeconds;
    minutes = (Math.floor(finalTimeInSeconds / 60));
    seconds = finalTimeInSeconds % 60;

    this.print();
  }

  this.subtract = function(inputMinutes, inputSeconds) {

    finalTimeInSeconds -= inputMinutes * 60 + inputSeconds;
    if (finalTimeInSeconds <= 0) {
      callback()
    }
    finalTimeInSeconds = (finalTimeInSeconds < 0) ? 0 : finalTimeInSeconds;
    minutes = (Math.floor(finalTimeInSeconds / 60));
    seconds = finalTimeInSeconds % 60;
    this.print();
  }

  this.reset = function() {

    this.set(0, 0);
  }

  this.print = function() {

    displayMinutes = (minutes.toString().length == 1) ? "0" + minutes : minutes; //ternary operator: adds a zero to the beggining 
    displaySeconds = (seconds.toString().length == 1) ? "0" + seconds : seconds; //of the number if it has only one caracter.

    $(element).text(displayMinutes + ":" + displaySeconds);
  }

  this.run = function() {

    if (this.running == false) {
      this.running = true;

      var _f = function() {
        secondStarted = new Date;
        self.subtract(0, 1);
        interval = 1000;
      }
      ac = setInterval(_f, interval);


    }
  }

  this.stop = function() {

    if (this.running == true) {
      this.running = false;
      console.log(this + "(" + element + ") was stopped");
      stopped = new Date;
      interval = 1000 - (stopped - secondStarted);
      clearInterval(ac);
    }
  }

  this.getTotalTimeInSeconds = function() {


    return finalTimeInSeconds;
  }

  this.reset();

}

function note(string) {
  alert(string);
}

function mainMenu() {
  //EMPTY BODY
  $("body").empty();
  $("body").append(
    //BUTTONS
    "<div id='playButton' class='mainButton'><div class='buttonText mainButtonText'>PLAY</div></div>"
  );
  //BINDS
  $("#playButton").bind("click", function(){
  	playMain();
    setUpSession();
  });

}

function playMain() {
  //EMPTY BODY
  $("body").empty();
  $("body").append(
    //TASK TEXT
    "<p class='text' id='taskText'>Lorem ipsum dolor sit amet.</p>",
    //TIMERS
    "<div id='taskTimerWrap'><p class='text timer' id='taskTimer'>00:00</p><p class='text' id='taskTimerText'>Task Time</p></div>",
    "<div id='globalTimerWrap'><p class='text timer' id='globalTimer'>00:00</p><p class='text' id='globalTimerText'>Global Time</p></div>",
    //TASK COUNTER
    "<div class='text' id='taskCounter'>0/0 tasks completed</div>"
  );
  //BINDS
  $("#taskText").bind("click", nextTask);
}
#taskText {
  text-align: center;
  display: table;
  vertical-align: middle;
  height: auto;
  width: 100%;
  top: 50px;
  bottom: 0;
  left: 0;
  right: 0;
  position: absolute;
  margin: auto;
  font-size: 65px;
  cursor: pointer;
}

#taskTimerWrap {
  text-align: center;
  top: 0;
  right: 0;
  left: 170px;
  margin: 5px;
  position: absolute;
  -webkit-transition: all 0.5s ease;
}

.timer {
  font-size: 64px;
  margin: 0;
  line-height: 0.88;
}

#taskTimerText {
  font-size: 34.4px;
  margin: 0;
  line-height: 0.65;
}

#globalTimerWrap {
  text-align: center;
  top: 0;
  left: 0;
  right: 170px;
  margin: 5px;
  position: absolute;
}

#globalTimerText {
  font-size: 28.5px;
  margin: 0;
  line-height: 0.78;
  transform: scale(1, 1.2);
}

#taskCounter {
  text-align: center;
  bottom: 0;
  right: 0;
  left: 0;
  width: auto;
  position: absolute;
  font-size: 30px;
  color: #98D8D9;
  -webkit-transition: all 0.5s ease;
}

#taskCounter:hover {
  color: #F1F2F0
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
like image 569
cabralpinto Avatar asked Dec 27 '15 20:12

cabralpinto


People also ask

Why JavaScript timer is unreliable?

The reason why this happens is because Javascript is single threaded. Which means unlike other programming languages where intensive processes run in a background thread, in Javascript, everything runs in the main thread.

Which of the following can be used to execute a function over and over again at specified time intervals?

You can use the JavaScript setInterval() method to execute a function repeatedly after a certain time period. The setInterval() method requires two parameters first one is typically a function or an expression and the other is time delay in milliseconds.

What is the working of timers in JavaScript?

In JavaScript, a timer is created to execute a task or any function at a particular time. Basically, the timer is used to delay the execution of the program or to execute the JavaScript code in a regular time interval. With the help of timer, we can delay the execution of the code.


1 Answers

In Timer.stop() you change the interval that's used for the next run. Changing the interval variable in _f() isn't going to change the interval used by setInterval().

In this case you will have to use setTimeout() instead:

function Timer(element, callback) {    
  var ac, minutes, seconds, finalTimeInSeconds, displayMinutes, displaySeconds, timeout = 1000,
    self = this,
    timeLeftToNextSecond = 1000;
  this.running = false;

  /* ... */

  this.run = function() {
    if (this.running == false) {
      this.running = true;

      var _f = function() {
        secondStarted = new Date;
        self.subtract(0, 1);        
        ac = setTimeout(_f, 1000);
      }
      ac = setTimeout(_f, timeout);
    }
  }

  this.stop = function() {    
    if (this.running == true) {
      this.running = false;
      console.log(this + "(" + element + ") was stopped");
      stopped = new Date;
      timeout = 1000 - (stopped - secondStarted);
      clearTimeout(ac);
    }
  }

  /* ... */    
}
like image 190
Johan Karlsson Avatar answered Sep 22 '22 08:09

Johan Karlsson