2008-12-03 216 views
14

這裏是場景:避免javascript競爭條件

我的用戶呈現一個網格,基本上,一個電子表格的精簡版。網格中的每一行都有文本框。當他們更改文本框中的值時,我將對其輸入進行驗證,更新驅動網格的集合,並在頁面上重新繪製小計。這全部由每個文本框的OnChange事件處理。

當他們點擊「保存」按鈕時,我使用按鈕的OnClick事件對金額進行一些最終驗證,然後將其全部輸入發送到Web服務並保存。

至少,如果他們通過表單選擇「提交」按鈕,會發生什麼情況。

問題是,如果他們輸入一個值,然後立即單擊保存按鈕,SaveForm()在UserInputChanged()完成之前開始執行 - 競爭條件。我的代碼不使用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是在保存代碼開始之前驗證代碼是否執行的神奇數字。這取決於用戶的計算機速度可能更高或更低。

有沒有人有任何想法如何處理這種情況?一位同事建議在驗證代碼運行時使用信號量,並在保存代碼中等待信號量解鎖時出現忙碌循環 - 但我想避免在代碼中使用任何類型的忙碌循環。

回答

18

使用信號量(我們稱之爲StillNeedsValidating)。如果SaveForm函數看到StillNeedsValidating信號量已啓動,讓它激活它自己的第二個信號量(我將在此處調用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; 
} 
+0

正是我需要的。謝謝! – 2008-12-03 18:13:48

4

我認爲超時造成您的問題......如果這將是普通的代碼(沒有異步AJAX調用,超時等),然後我不認爲UserInputChanged完成之前SaveForm將被執行。

+0

我同意。也許一個更好的「慢代碼」模擬是隻有一個大的「for」循環,它什麼也不做。這樣你就不會釋放對JavaScript引擎的控制,並應確保事件按照正確的順序處理。 – 2008-12-03 18:14:03

+0

實際代碼中沒有setTimeouts。有一系列無聊的getElementByIds,一個isNan,一個parseInt和一個檢查,以確保所有文本框的總和不超過預定義的數量 - 但沒有異步。 – 2008-12-03 18:23:19

7

驗證期間禁用保存按鈕。 將它設置爲禁用,作爲驗證的第一件事,並在完成時重新啓用它。

例如

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並驗證一個功能調整,但除非你的用戶有超人的反應能力,你要善於去。

0

您可以設置一個循環函數來監視整個網格的狀態,並引發一個事件來指示整個網格是否有效。

然後,您的「提交表單」按鈕將根據該狀態啓用或禁用其自身。

哦,我現在看到類似的迴應 - 當然也有效。

1

信號量或互斥量可能是最好的方式,但不是繁忙的循環,只需使用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; 
} 
1

你沒有競爭條件,比賽條件不能在JavaScript中發生,因爲JavaScript是單線程的,因此2個線程不能相互干擾。

你給的例子不是一個很好的例子。 setTimeout調用將被調用的函數放入JavaScript引擎的隊列中,並在稍後運行。如果此時您單擊保存按鈕,setTimeout函數將不會被調用,直到保存完成後。

JavaScript中可能發生的事情是onClick事件在調用onChange事件之前由javascript引擎調用。

作爲提示,請記住,JavaScript是單線程的,除非您使用JavaScript調試器(firebug,microsoft screipt debugger)。那些程序攔截線程並暫停它。從那時起,其他線程(通過事件,setTimeout調用或XMLHttp處理程序)就可以運行,看起來javascript可以同時運行多個線程。

0

當使用異步數據源時,您肯定會有競爭狀況,因爲JavaScript進程線程繼續執行可能依賴於尚未從遠程數據源返回的數據的指令。這就是爲什麼我們有回調函數。

在您的示例中,對驗證代碼的調用需要有一個回調函數,可以在驗證返回時執行某些操作。

但是,當製作複雜的邏輯或嘗試排除故障或增強現有的一系列回調時,您可以堅持下去。

這就是我之所以創建的原-Q庫:http://code.google.com/p/proto-q/

檢查出來,如果你做了很多這種類型的工作的。