2008-12-03 117 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/

检查出来,如果你做了很多这种类型的工作的。