シナリオは次のとおりです。
私のユーザーには、基本的にはスプレッドシートの簡略版であるグリッドが表示されます。グリッドの各行にテキストボックスがあります。彼らがテキストボックスの値を変更するとき、私は彼らの入力に対して検証を実行し、グリッドを駆動しているコレクションを更新し、ページ上の小計を再描画しています。これはすべて、各テキストボックスのOnChangeイベントによって処理されます。
彼らが[保存]ボタンをクリックすると、ボタンの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関数を呼び出します。
ジャンクコードで;
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;
}
検証中は保存ボタンを無効にします。検証が最初に行うように無効に設定し、終了したら再度有効にします。
例えば.
function UserInputChanged(control) {
// --> disable button here --<
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
そして
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/
この種の仕事をたくさんするなら、それをチェックしてください。
グリッド全体の状態を監視し、グリッド全体が有効かどうかを示すイベントを発生させる定期的な関数を設定できます。
「フォームの送信」ボタンは、そのステータスに基づいてそれ自体を有効または無効にします。
ああ、私は今同様の反応を見ます-もちろんそれもうまくいきます。