環境
更新にそれぞれ 5 秒かかる複雑なグラフが約 10 個あります。これらの 10 個のグラフをループすると、更新に約 50 秒かかります。この 50 秒間、ユーザーはスクロールバーを動かすことができます。スクロールバーが移動した場合、リフレッシュは停止する必要があり、スクロールバーの移動が停止すると、リフレッシュが再び発生します。
インターフェイスを更新するために、ループ内で setTimeout 関数を使用しています。アルゴリズムは次のとおりです。
- 最初のグラフをレンダリングする
- setTimeout(2 番目のグラフをレンダリング、200)
- 2 番目のグラフがレンダリングされると、3 番目のグラフが 200 ミリ秒でレンダリングされます。
setTimeout を使用すると、スクロールバー イベントをキャッチし、次の更新時に clearTimeout して、スクロールバーを移動する前に 50 秒待機することを回避できます...
問題は、いつでも実行できないことです。
次の簡単なコードを使用してください (このフィドルで試すことができます: 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);
}
コードは次のことを行います。
- マウスを赤い div に移動すると、カウンターが更新されます
- 赤い div をクリックすると、1 秒の大きな処理がシミュレートされます (そのため、javascript のモノ スレッドが原因でインターフェイスがフリーズします)。
- フリーズ後、マウスが動くまで1ms待ち、処理のやり直し等を行う
- マウスが再び動くと、タイムアウトを中断して無限ループを回避します。
問題
フリーズ中に1回クリックしてマウスを動かすと、setTimeoutが発生したときに実行される次のコードは、mousemoveイベントのコードであると考えていました(したがって、タイムアウトとフリーズをキャンセルします)しかし、時々クリックのカウンターは、movemove イベントにより 1 ポイントだけではなく、2 ポイント以上獲得します...
このテストの結論: setTimeout 関数は、mousemove イベント中にコードを実行するために常にリソースを解放するとは限りませんが、別のコードを実行する前にスレッドを保持し、settimeout コールバック内でコードを実行することがあります。
これの影響は、実際の例では、ユーザーがスクロールバーを使用する前に 5 秒待つのではなく、10 秒待つことができることです (2 つのグラフがレンダリングされます)。これは非常に煩わしく、これを回避し、レンダリング フェーズ中にスクロールバーが移動したときに 1 つのグラフのみがレンダリングされる (他のグラフはキャンセルされる) ようにする必要があります。
マウスが動いたときにタイムアウトを確実に破るにはどうすればよいですか?
PS: 以下の単純な例では、タイムアウトを 200 ミリ秒に更新すると、すべてが完全に実行されますが、許容できる解決策ではありません (実際の問題はより複雑で、問題は 200 ミリ秒のタイマーと複雑なインターフェイスで発生します)。「グラフのレンダリングを最適化する」などの解決策を提供しないでください。これはここでは問題ではありません。
EDIT:cernunnosには問題のより良い説明があります:また、ループ上のプロセスを「ブロック」することで、そのループが終了するまでイベントを処理できないようにするため、イベントはその間にのみ処理されます(そしてタイムアウトはクリアされます)各ループの実行(したがって、中断する前に2回以上の完全な実行を待たなければならないことがあります) .
問題は太字の言葉で正確に含まれています: 必要なときに必ず実行を中断し、中断する前に2回以上の完全な実行を待たないようにしたい
2番目の編集:
要約すると、次の jsfiddle を取ります: http://jsfiddle.net/BwNca/5/ (上記のコード)。
この jsfiddle を更新して、次のソリューションを提供します。
赤い div でマウスを動かします。次に、クリックして移動を続けます。右のカウンターは 1 回だけ上げる必要があります。しかし、最初のカウンターが再び実行される前に、2 回または 3 回レイズすることがあります...これが問題です。1 回だけレイズする必要があります。