JavaScriptの競合状態を回避する
-
19-08-2019 - |
質問
シナリオは次のとおりです。
ユーザーには、グリッド、基本的にはスプレッドシートの簡略版が表示されます。グリッドの各行にはテキストボックスがあります。テキストボックスの値を変更するとき、入力の検証を実行し、グリッドを駆動するコレクションを更新し、ページの小計を再描画します。これはすべて、各テキストボックスのOnChangeイベントによって処理されます。
彼らが<!> quot; Save <!> quotをクリックすると;ボタン、ボタンのOnClickイベントを使用して金額の最終検証を実行し、入力全体をWebサービスに送信して保存します。
少なくとも、フォームをタブで移動して[送信]ボタンに移動すると、それが起こります。
問題は、値を入力してすぐに保存ボタンをクリックすると、UserInputChanged()が完了する前にSaveForm()が実行を開始することです。これは競合状態です。私のコードはsetTimeoutを使用していませんが、緩慢なUserInputChanged検証コードをシミュレートするために使用しています:
<!-- snip -->
<script>
var amount = null;
var currentControl = null;
function UserInputChanged(control) {
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
function SaveForm() {
// call web service to save value
document.getElementById("SavedAmount").innerHTML = amount;
}
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount; // update subtotals
}
</script>
<!-- snip -->
Amount: <input type="text" id="UserInputValue" onchange="UserInputChanged(this);" /> <br />
Subtotal: <span id="Subtotal"></span> <br />
<input type="button" onclick="SaveForm();" value="Save" /> <br /><br />
Saved amount: <span id="SavedAmount"></span>
<!-- snip -->
検証コードを高速化できるとは思いません-かなり軽量ですが、明らかに、検証が完了する前にコードがWebサービスを呼び出そうとするほど遅いのです。
私のマシンでは、〜95msは、保存コードが開始される前に検証コードが実行されるかどうかの間のマジックナンバーです。これは、ユーザーのコンピューターの速度に応じて、より高くも低くもなります。
この状態をどのように処理するか、誰にもアイデアはありますか?同僚は、検証コードの実行中にセマフォを使用することと、セマフォがロック解除されるまで待機するビジーループの保存コードを使用することを提案しましたが、コードでビジーループを使用しないようにしたいと思います。
解決
セマフォを使用します(StillNeedsValidatingと呼びましょう)。 SaveForm関数がStillNeedsValidatingセマフォが起動していることを確認したら、独自の2番目のセマフォ(ここではFormNeedsSavingと呼びます)をアクティブにして戻ります。検証関数が終了すると、FormNeedsSavingセマフォが起動している場合、SaveForm関数を独自に呼び出します。
jankcodeで;
function UserInputChanged(control) {
StillNeedsValidating = true;
// do validation
StillNeedsValidating = false;
if (FormNeedsSaving) saveForm();
}
function SaveForm() {
if (StillNeedsValidating) { FormNeedsSaving=true; return; }
// call web service to save value
FormNeedsSaving = false;
}
他のヒント
検証中に保存ボタンを無効にします。 検証が最初に行うように無効に設定し、終了したら再度有効にします。
e.g。
function UserInputChanged(control) {
// --> disable button here --<
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
and
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount; // update subtotals
// --> enable button here if validation passes --<
}
setTimeoutを削除して検証を1つの機能にする場合は調整する必要がありますが、ユーザーが超人的な反射神経を持たない限り、行ってください。
タイムアウトが問題を引き起こしていると思います...それが単純なコード(非同期AJAX呼び出し、タイムアウトなどなし)になる場合、UserInputChangedが完了する前にSaveFormが実行されるとは思いません。
セマフォまたはミューテックスがおそらく最善の方法ですが、ビジーループの代わりに、setTimeout()
を使用してスレッドスリープをシミュレートするだけです。このように:
busy = false;
function UserInputChanged(control) {
busy = true;
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
function SaveForm() {
if(busy)
{
setTimeout("SaveForm()", 10);
return;
}
// call web service to save value
document.getElementById("SavedAmount").innerHTML = amount;
}
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount; // update subtotals
busy = false;
}
グリッド全体の状態を監視し、グリッド全体が有効かどうかを示すイベントを発生させる繰り返し機能を設定できます。
「フォームを送信」ボタンは、そのステータスに基づいて自身を有効または無効にします。
ああ、今は同じような反応が見られます-もちろんそれも機能します。
非同期データソースを操作する場合、JavaScriptプロセススレッドはリモートデータソースからまだ返されていないデータに依存する可能性のあるディレクティブを引き続き実行するため、競合状態が発生する可能性があります。それがコールバック関数を持っている理由です。
例では、検証コードの呼び出しには、検証が戻ったときに何かを実行できるコールバック関数が必要です。
ただし、複雑なロジックを使用して何かを作成したり、既存の一連のコールバックをトラブルシューティングまたは強化しようとする場合は、気を付けてください。
これがproto-qライブラリを作成した理由です: http://code.google .com / p / proto-q /
この種の作業をたくさん行う場合は、チェックしてください。
競合状態はありません。javascriptはシングルスレッドであるため、javascriptで競合状態は発生せず、2つのスレッドが互いに干渉することはありません。
あなたが与える例はあまり良い例ではありません。 setTimeout呼び出しは、呼び出された関数をjavascriptエンジンのキューに入れ、後で実行します。その時点で[保存]ボタンをクリックすると、保存が完全に終了するまでsetTimeout関数は呼び出されません。
おそらくjavascriptで起こっていることは、onChangeイベントが呼び出される前にjavascriptエンジンによってonClickイベントが呼び出されることです。
ヒントとして、javascriptデバッガー(firebug、microsoft screiptデバッガー)を使用しない限り、javascriptはシングルスレッドであることに注意してください。これらのプログラムはスレッドをインターセプトして一時停止します。その時点から、他のスレッドで(イベント、setTimeout呼び出し、またはXMLHttpハンドラーを介して)実行でき、javascriptが同時に複数のスレッドを実行できるように見えます。