Context
I've got about 10 complex graphs which take 5sec each to refresh. If I do a loop on these 10 graphs, it takes about 50 seconds to refresh. During these 50 seconds, the user can move a scrollbar. If the scrollbar is moved, the refresh must stop and when the scrollbar stops to move, the refresh occurs again.
I'm using the setTimeout function inside the loop to let the interface refresh. the algorithm is :
- render the first graph
- setTimeout(render the second graph, 200)
- when the second graph is rendered, render the third one in 200ms, and so on
The setTimeout allows us to catch the scrollbar event and to clearTimeout the next refresh to avoid to wait 50sec before moving the scrollbar...
The problem is that it does not run anytime.
Take the simple following code (you can try it in this fiddle : http://jsfiddle.net/BwNca/5/) :
HTML :
<div id="test" style="width: 300px;height:300px; background-color: red;">
</div>
<input type="text" id="value" />
<input type="text" id="value2" />
Javascript :
var i = 0;
var j = 0;
var timeout;
var clicked = false;
// simulate the scrollbar update : each time mouse move is equivalent to a scrollbar move
document.getElementById("test").onmousemove = function() {
// ignore first move (because onclick send a mousemove event)
if (clicked) {
clicked = false;
return;
}
document.getElementById("value").value = i++;
clearTimeout(timeout);
}
// a click simulates the drawing of the graphs
document.getElementById("test").onclick = function() {
// ignore multiple click
if (clicked) return;
complexAlgorithm(1000);
clicked = true;
}
// simulate a complexe algorithm which takes some time to execute (the graph drawing)
function complexAlgorithm(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
document.getElementById("value2").value = j++;
// launch the next graph drawing
timeout = setTimeout(function() {complexAlgorithm(1000);}, 1);
}
The code does :
- when you move your mouse into the red div, it updates a counter
- when you click on the red div, it simulates a big processing of 1sec (so it freezes the interface due to javascript mono thread)
- after the freezing, wait 1ms, and resimulate the processing and so on until the mouse move again
- when the mouse move again, it breaks the timeout to avoid infinite loop.
The problem
When you click one time and move the mouse during the freeze, I was thinking that the next code that will be executed when a setTimeout will occurs is the code of the mousemove event (and so it will cancel the timeout and the freeze) BUT sometimes the counter of click gains 2 or more points instead of gaining only 1 point due to the mouvemove event...
Conclusion of this test : the setTimeout function does not always release resource to execute a code during a mousemove event but sometimes kept the thread and execute the code inside the settimeout callback before executing another code.
The impact of this is that in our real example, the user can wait 10 sec (2 graphs are rendered) instead of waiting 5 seconds before using the scrollbar. This is very annoying and we need to avoid this and to be sure that only one graph is rendered (and other canceled) when the scrollbar is moved during a render phase.
How to be sure to break the timeout when the mouse move ?
PS: in the simple example below, if you update the timeout with 200ms, all runs perfectly but it is not an acceptable solution (the real problem is more complex and the problem occurs with a 200ms timer and a complex interface). Please do not provide a solution as "optimize the render of the graphs", this is not the problem here.
EDIT : cernunnos has a better explanation of the problem : Also, by "blocking" the process on your loop you are ensuring no event can be handled until that loop has finished, so any event will only be handled (and the timeout cleared) inbetween the execution of each loop (hence why you sometimes have to wait for 2 or more full executions before interrupting).
The problem is exactly contains in bold words : I want to be sure to interrupt the execution when I want and not to wait 2 or more full executions before interrupting
Second EDIT :
In summary : takes this jsfiddle : http://jsfiddle.net/BwNca/5/ (the code above).
Update this jsfiddle and provide a solution to :
Mouse move on the red div. Then click and continue moving : the right counter must raise only once. But sometimes it raises 2 or 3 times before the first counter can run again... this is the problem, it must raise only once !
Any long-running function is going to tie up your browser window. Consider moving your complexAlgorithm() outside of your main javascript code using WebWorkers.