序文:
他のいくつかの答えは正しいですが、実際に解決されている問題が何であるかを説明していないため、この詳細な説明を提示するためにこの答えを作成しました。
そのため、私はブラウザが何をするか、そしてどのように使用setTimeout()
することが役立つかの詳細なウォークスルーを投稿しています。見た目は長めですが、実際には非常にシンプルで簡単です-非常に詳細にしただけです。
更新:以下の説明を実演するためにJSFiddleを作成しました:http ://jsfiddle.net/C2YBE/31/ 。多くのおかげで、それをキックスタートを支援するための@ThangChungへ。
UPDATE2: JSFiddleのWebサイトが停止したり、コードが削除されたりした場合に備えて、この回答の最後にコードを追加しました。
詳細:
「何かをする」ボタンと結果divを備えたWebアプリを想像してみてください。
onClick
「何かを行う」ボタンのハンドラーは、関数「LongCalc()」を呼び出し、2つのことを行います。
非常に長い計算を行います(たとえば3分かかります)
計算結果を結果divに出力します。
ユーザーがこれをテストし始め、「何かを行う」ボタンをクリックすると、ページはそこに座って3分間何もしないように見え、落ち着きがなくなり、もう一度ボタンをクリックし、1分待って、何も起こらず、もう一度ボタンをクリックします...
問題は明白です-何が起こっているかを示す「ステータス」DIVが必要です。それがどのように機能するか見てみましょう。
したがって、「ステータス」DIV(最初は空)を追加し、onclick
ハンドラー(関数LongCalc()
)を変更して4つのことを行います。
ステータス「計算中... 3分ほどかかる場合があります」をステータスDIVに入力します
非常に長い計算を行います(たとえば3分かかります)
計算結果を結果divに出力します。
ステータス「計算完了」をステータスDIVに入力します
また、アプリをユーザーに喜んで提供して、再テストしてもらいます。
彼らは非常に怒ってあなたに戻ってきます。また、ボタンをクリックしたときに、ステータスDIVが「計算中...」のステータスで更新されなかったことを説明します。
頭をかき、StackOverflowで質問して(またはドキュメントやgoogleを読んで)、問題を認識します。
ブラウザーは、イベントに起因するすべての「TODO」タスク(UIタスクとJavaScriptコマンドの両方)を単一のキューに配置します。残念ながら、新しい「計算中...」の値で「ステータス」DIVを再描画すると、キューの最後に移動する別のTODOになります。
ユーザーのテスト中のイベントの内訳と、各イベント後のキューの内容は次のとおりです。
- キュー:
[Empty]
- イベント:ボタンをクリックします。イベント後のキュー:
[Execute OnClick handler(lines 1-4)]
- イベント:OnClickハンドラーの最初の行を実行します(ステータスDIV値の変更など)。イベント後のキュー:
[Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]
。DOMの変更は瞬時に行われますが、対応するDOM要素を再描画するには、DOMの変更によってトリガーされ、キューの最後に移動した新しいイベントが必要です。
- 問題!!! 問題!!!詳細は以下で説明します。
- イベント:ハンドラー(計算)で2行目を実行します。後のキュー:
[Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value]
。
- イベント:ハンドラーで3行目を実行します(結果DIVに入力)。後のキュー:
[Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result]
。
- イベント:ハンドラーで4行目を実行(ステータスDIVに "DONE"を入力)。キュー:
[Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value]
。
- イベント:ハンドラサブ
return
から暗黙的に実行されonclick
ます。「Execute OnClickハンドラー」をキューから取り出し、キューの次の項目の実行を開始します。
- 注:既に計算が完了しているため、ユーザーには3分がすでに経過しています。再描画イベントはまだ発生していません!!!
- イベント:ステータスDIVを「計算中」の値で再描画します。再描画を行い、それをキューから削除します。
- イベント:結果値で結果DIVを再描画します。再描画を行い、それをキューから削除します。
- イベント:「完了」の値でステータスDIVを再描画します。再描画を行い、それをキューから削除します。鋭い目で見る人は、「計算中」の値を持つステータスDIVがマイクロ秒の端数で点滅することにさえ気付くかもしれません- 計算が終了した後
つまり、根本的な問題は、「ステータス」DIVの再描画イベントが最後にキューに配置されることです。3分かかる「実行ライン2」イベントの後、実際の再描画は、計算が行われた後。
救助に来るsetTimeout()
。それはどのように役立ちますか?を介して長時間実行コードを呼び出すことによりsetTimeout
、実際には2つのイベントが作成されます。setTimeout
実行自体と、(タイムアウトが0であるため)、実行されるコードの個別のキューエントリです。
したがって、問題を解決するには、onClick
ハンドラーを2つのステートメント(新しい関数または内のブロックのみonClick
)に変更します。
ステータス「計算中... 3分ほどかかる場合があります」をステータスDIVに入力します
setTimeout()
タイムアウト0で実行し、LongCalc()
関数を呼び出します。
LongCalc()
機能は前回とほぼ同じですが、最初のステップとして明らかに「計算中...」ステータスDIV更新がありません。代わりにすぐに計算を開始します。
では、イベントシーケンスとキューは現在どのように見えますか?
- キュー:
[Empty]
- イベント:ボタンをクリックします。イベント後のキュー:
[Execute OnClick handler(status update, setTimeout() call)]
- イベント:OnClickハンドラーの最初の行を実行します(ステータスDIV値の変更など)。イベント後のキュー:
[Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value]
。
- イベント:ハンドラーの2行目を実行(setTimeout呼び出し)。後のキュー:
[re-draw Status DIV with "Calculating" value]
。キューには、あと0秒間新しいものはありません。
- イベント:タイムアウトからのアラームが0秒後に鳴ります。後のキュー:
[re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)]
。
- イベント:「計算中」の値でステータスDIVを再描画します。後のキュー:
[execute LongCalc (lines 1-3)]
。この再描画イベントは、アラームが鳴る前に実際に発生する可能性があることに注意してください。これは同様に機能します。
- ...
やったー!計算が始まる前に、ステータスDIVが「計算中...」に更新されました!!!
以下は、これらの例を示すJSFiddleのサンプルコードです:http : //jsfiddle.net/C2YBE/31/ :
HTMLコード:
<table border=1>
<tr><td><button id='do'>Do long calc - bad status!</button></td>
<td><div id='status'>Not Calculating yet.</div></td>
</tr>
<tr><td><button id='do_ok'>Do long calc - good status!</button></td>
<td><div id='status_ok'>Not Calculating yet.</div></td>
</tr>
</table>
JavaScriptコード:(onDomReady
jQuery 1.9で実行され、必要になる場合があります)
function long_running(status_div) {
var result = 0;
// Use 1000/700/300 limits in Chrome,
// 300/100/100 in IE8,
// 1000/500/200 in FireFox
// I have no idea why identical runtimes fail on diff browsers.
for (var i = 0; i < 1000; i++) {
for (var j = 0; j < 700; j++) {
for (var k = 0; k < 300; k++) {
result = result + i + j + k;
}
}
}
$(status_div).text('calculation done');
}
// Assign events to buttons
$('#do').on('click', function () {
$('#status').text('calculating....');
long_running('#status');
});
$('#do_ok').on('click', function () {
$('#status_ok').text('calculating....');
// This works on IE8. Works in Chrome
// Does NOT work in FireFox 25 with timeout =0 or =1
// DOES work in FF if you change timeout from 0 to 500
window.setTimeout(function (){ long_running('#status_ok') }, 0);
});