Cake Cake - 5 months ago 10
Ajax Question

JavaScript | Difficulties with while-loop and timer

I'm working on a Pomodoro-esque timer. The timer must be able to do the following:

When 'Start' button is pressed:


  • Start 5 second timer. Times will change, of course)

  • After timer expires, send AJAX event with start and end timestamp

  • Start a 3 second timer.

  • After timer expires, start a new 5 second timer. (Loop step 1 to 3 a total of FOUR times)

  • At the end of the fourth loop, start a 7 second timer.

  • After the 7 second timer expires, start the 5-3 loop again.



When 'Stop' button is pressed:


  • Cancel the timer, reset it back to the default state.



This is the code I have now:

HTML

<html>
<head>
<meta charset="UTF-8">
<title>Pomodoro Test</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<div id="test"></div>
<div id="timer">5</div>

<button id="start">Start</button>
<button id="reset">Reset</button>

<div id="startTime"></div>
<div id="endTime"></div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
<script src="functions.js"></script>
</body>
</html>


JavaScript:

var timer = 5;
var flag = 0;
var startTime;
var endTime;
var intervalTimer;

function update() {
if (timer-- > 0) {
$("#timer").html(timer); // retrieve value from html
} else {
endTime = new Date();
//alert("Start Time = " + startTime + "\nEnd Time = " + endTime);
$("#endTime").html("End time: " + endTime.toTimeString());
resetTimer();


// TODO: send ajax call to server, sending startTime and endTime
// so that these can be saved to database

}
}
function loopingTimer() {
while(flag === 0) {
startTime = new Date();
$("#startTime").html("Start time: " + startTime.toTimeString());
startTimer();
i++;
setTimeout(function() {
if(i < 5) {
loopingTimer();
}
}, 3001)}};

$("#start").click(function() {
loopingTimer();
});

$("#reset").click(function() {
flag = 1;
});



function startTimer() {
intervalTimer = window.setInterval(update, 500);
}

function resetTimer() {
stopTimer();
timer = 5;
$("#timer").text(timer);
}

function stopTimer() {
window.clearInterval(intervalTimer);
}


I find it extremely difficult to get the desired functionality, I've been tinkering with this code for 2 days. I sincerely hope you guys can help me out.

Cake.

Answer

The while loop, in the loopingTimer function, does (simplified):

while (userDidntStop) {
    startTimer();
}

The break condition is that the user clicks on a button. If the user does so after 500 loopings (a very very very short time), then this loop will have created about 500 timers. The biggest issue is here: avoid running timers (or intervals the same) within loops.

Another issue is that startTime is reset in the almost-infinite loop. You could merge loopingTimer with startTimer, as they do basically a single thing, which is starting the timer, and thus preventing

Also, you use the timer variable to check for the elapsed time. Browsers do not have the obligation to provide the specified delays (at least for setTimeout, according to the MDN documentation. I think it is also the case for setInterval). Therefore, it is safer to instantiate a new Date(), in order to always have the right time.

Also, it is posible to store the sequence of timers somewhere (ie, in an array), to allow modifying this sequence at any time, and also looping easily through it.

Following these points, I would have written something like the following code. Remember that this is an illustration, it might not produce your desired behavior.

var timeLoop = [5, 3, 5, 3, 5, 3, 5, 3, 7]; // seconds
var timeIterator = timeLoop.length;
var startTime;
var endTime;
var intervalTimer;

function sendTimer() { // once the timer ends, send it
    // send AJAX request with startTime and endTime here
}

function updateTimer() { // main timer loop
    var now = Date.now(); // make sure you have the right time
    var diff = endTime - now;
    if (diff <= 0) { // if endTime is past, stop and send the timer
        diff = 0;
        clearInterval(intervalTimer);
        sendTimer();
    }
    // Update the displayed time using diff (ms)
}

function resetTimer() { // to call on $("#reset").click
    if (endTime > Date.now()) {
        endTime = Date.now();
        updateTimer(); // reset the displayed time, and stop updateTimer
        // note that this will call sendTimer() with the _aborted_ time
    }
}

function startTimer() { // to call on $("#start").click
    resetTimer();
    // We will loop throught the timeLoop every time we start the timer
    if (timeLoop.length < ++timeIterator) {
        timeIterator = 0;
    }
    startTime = Date.now();
    endTime = startTime + 1000 * timeLoop[timeIterator];
    intervalTimer = setInterval(updateTimer, 500);
}
Comments