27

我的用户看到的基本上是一个精简版的电子表格。网格中的每一行都有文本框。当他们更改文本框中的值时,我正在对他们的输入执行验证,更新驱动网格的集合,并重新绘制页面上的小计。这一切都由OnChange每个文本框的事件处理。

当他们单击Save按钮时,我正在使用按钮的OnClick事件对金额执行一些最终验证,然后将他们的整个输入发送到 Web 服务,并保存它。

至少,如果他们通过表单切换到Submit按钮,就会发生这种情况。

问题是,如果他们输入一个值,然后立即单击保存按钮,在完成SaveForm()之前开始执行UserInputChanged()——竞争条件。我的代码不使用setTimeout,但我用它来模拟缓慢的UserInputChanged验证代码:

 <script>
  var amount = null;
  var currentControl = null;

  function UserInputChanged(control) {
      currentControl = control;
      // use setTimeout to simulate slow validation code
      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;
  }
</script>

Amount:   <input type="text" onchange="UserInputChanged(this)">
Subtotal: <span id="Subtotal"></span>
<button onclick="SaveForm()">Save</button>
Saved amount: <span id="SavedAmount"></span>

我认为我无法加快验证代码的速度——它非常轻量级,但显然速度很慢,以至于代码会在验证完成之前尝试调用 Web 服务。

在我的机器上,~95ms 是在保存代码开始之前验证代码是否执行之间的幻数。这可能更高或更低,具体取决于用户的计算机速度。

有谁知道如何处理这种情况?一位同事建议在验证代码运行时使用信号量,并在保存代码中使用繁忙循环等待信号量解锁 - 但我想避免在我的代码中使用任何类型的繁忙循环。

4

7 回答 7

27

使用信号量(我们称之为 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;
}
于 2008-12-03T18:08:24.160 回答
11

在验证期间禁用保存按钮。作为验证的第一件事将其设置为禁用,并在完成时重新启用它。

例如

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 并使验证成为一项功能时,您必须进行调整,但除非您的用户具有超人的反应能力,否则您应该很高兴。

于 2008-12-03T18:10:48.307 回答
4

我认为超时会导致您的问题...如果这将是纯代码(没有异步 AJAX 调用、超时等),那么我认为 SaveForm 不会在 UserInputChanged 完成之前执行。

于 2008-12-03T18:09:00.883 回答
4

信号量或互斥锁可能是最好的方法,但不要使用繁忙的循环,只需使用 asetTimeout()来模拟线程睡眠。像这样:

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;
}
于 2008-12-03T18:13:50.187 回答
0

您可以设置一个循环函数来监视整个网格的状态并引发一个指示整个网格是否有效的事件。

然后,您的“提交表单”按钮将根据该状态启用或禁用自身。

哦,我现在看到了类似的回应——当然,这也有效。

于 2008-12-03T18:12:48.910 回答
0

在使用异步数据源时,您肯定会遇到竞争条件,因为 JavaScript 进程线程继续执行可能依赖于尚未从远程数据源返回的数据的指令。这就是我们有回调函数的原因。

在您的示例中,对验证代码的调用需要有一个回调函数,该函数可以在验证返回时执行某些操作。

但是,当制作具有复杂逻辑的东西或尝试排除故障或增强现有的一系列回调时,您可能会发疯。

这就是我创建 proto-q 库的原因:http ://code.google.com/p/proto-q/

如果您做了很多此类工作,请检查一下。

于 2010-09-17T17:47:25.077 回答
-3

您没有竞态条件,因为 javascript 是单线程的,所以 javascript 中不会发生竞态条件,因此 2 个线程不能相互干扰。

你举的例子不是一个很好的例子。setTimeout 调用会将被调用的函数放入 javascript 引擎中的队列中,稍后再运行。如果此时您单击保存按钮,则在保存完全完成后才会调用 setTimeout 函数。

您的 javascript 中可能发生的情况是 onClick 事件在调用 onChange 事件之前由 javascript 引擎调用。

作为提示,请记住 javascript 是单线程的,除非您使用 javascript 调试器(firebug、microsoft screipt 调试器)。这些程序拦截线程并暂停它。从那时起,其他线程(通过事件、setTimeout 调用或 XMLHttp 处理程序)可以运行,这使得 javascript 似乎可以同时运行多个线程。

于 2008-12-03T19:55:30.747 回答