27

My users are presented a basically a stripped down version of a spreadsheet. There are textboxes in each row in the grid. When they change a value in a textbox, I'm performing validation on their input, updating the collection that's driving the grid, and redrawing the subtotals on the page. This is all handled by the OnChange event of each textbox.

When they click the Save button, I'm using the button's OnClick event to perform some final validation on the amounts, and then send their entire input to a web service, saving it.

At least, that's what happens if they tab through the form to the Submit button.

The problem is, if they enter a value, then immediately click the save button, SaveForm() starts executing before UserInputChanged() completes -- a race condition. My code does not use setTimeout, but I'm using it to simulate the sluggish UserInputChanged validation code:

 <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>

I don't think I can speed up the validation code -- it's pretty lightweight, but apparently, slow enough that code tries to call the web service before the validation is complete.

On my machine, ~95ms is the magic number between whether the validation code executes before the save code begins. This may be higher or lower depending on the users' computer speed.

Does anyone have any ideas how to handle this condition? A coworker suggested using a semaphore while the validation code is running and a busy loop in the save code to wait until the semaphore unlocks - but I'd like to avoid using any sort of busy loop in my code.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Jeremy Frey
  • 2,334
  • 2
  • 22
  • 26

7 Answers7

28

Use the semaphore (let's call it StillNeedsValidating). if the SaveForm function sees the StillNeedsValidating semaphore is up, have it activate a second semaphore of its own (which I'll call FormNeedsSaving here) and return. When the validation function finishes, if the FormNeedsSaving semaphore is up, it calls the SaveForm function on its own.

In 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;
}
zaratustra
  • 8,148
  • 8
  • 36
  • 42
12

Disable the save button during validation. Set it to disabled as the first thing validation does, and re-enable it as it finishes.

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 --<
}

You'll have to adjust when you remove the setTimeout and make the validation one function, but unless your users have superhuman reflexes, you should be good to go.

Andrew Rollings
  • 14,340
  • 7
  • 51
  • 50
4

I think the timeout is causing your problem... if that's going to be plain code (no asynchronous AJAX calls, timeouts etc) then I don't think that SaveForm will be executed before UserInputChanged completes.

Greg
  • 316,276
  • 54
  • 369
  • 333
  • I agree. Perhaps a better simulation of "slow code" is to just have a large "for" loop that does nothing. That way you're not releasing control of the Javascript engine and should ensure that the events are handled in the proper order. – Marc Novakowski Dec 03 '08 at 18:14
  • There's no setTimeouts in the actual code. There's a series of boring getElementByIds, an isNan, a parseInt, and a check to make sure that the sum of all textboxes doesn't exceed a pre-defined amount -- but nothing async. – Jeremy Frey Dec 03 '08 at 18:23
4

A semaphore or mutex is probably the best way to go, but instead of a busy loop, just use a setTimeout() to simulate a thread sleep. Like this:

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;
}
AllInOne
  • 1,450
  • 2
  • 14
  • 32
Excel Kobayashi
  • 578
  • 1
  • 6
  • 19
0

You could set up a recurring function that monitors the state of the entire grid and raises an event that indicates whether the entire grid is valid or not.

Your 'submit form' button would then enable or disable itself based on that status.

Oh I see a similar response now - that works too, of course.

neonski
  • 955
  • 4
  • 9
0

When working with async data sources you can certainly have race conditions because the JavaScript process thread continues to execute directives that may depend on the data which has not yet returned from the remote data source. That's why we have callback functions.

In your example, the call to the validation code needs to have a callback function that can do something when validation returns.

However, when making something with complicated logic or trying to troubleshoot or enhance an existing series of callbacks, you can go nuts.

That's the reason I created the proto-q library: http://code.google.com/p/proto-q/

Check it out if you do a lot of this type of work.

AutoSponge
  • 1,444
  • 10
  • 7
-4

You don't have a race condition, race conditions can not happen in javascript since javascript is single threaded, so 2 threads can not be interfering with each other.

The example that you give is not a very good example. The setTimeout call will put the called function in a queue in the javascript engine, and run it later. If at that point you click the save button, the setTimeout function will not be called until AFTER the save is completely finished.

What is probably happening in your javascript is that the onClick event is called by the javascript engine before the onChange event is called.

As a hint, keep in mind that javascript is single threaded, unless you use a javascript debugger (firebug, microsoft screipt debugger). Those programs intercept the thread and pause it. From that point on other threads (either via events, setTimeout calls or XMLHttp handlers) can then run, making it seem that javascript can run multiple threads at the same time.

  • 15
    Of course you can have race conditions in Javascript, it's highly asynchronous! There's only one UI thread, but any – apinstein Jul 27 '09 at 03:05
  • 3
    He is right, you cannot have a race condition in single thread with no I/O. There is no race condition here, with setTimeout. JS is asynchronous but based on an event loop which is single thread, so the event loop won't be call until the stack is empty. So the behavior of the program is completely determined you just have to read correctly the workflow of the code, and you will know when the event loop will be called. Unlike an race condition where it is really undetermined because you have several concurrent threads. – jillro Jan 30 '14 at 10:26
  • 4
    Incorrect. You can have race conditions caused by different orders of events. Typically this happens with threads in say, Java, but race conditions are intrinsically about uncertain event/state orders and how to handle those appropriately. What he is describing is precisely a race condition, and the right solution as posted above is to use a semaphore style design pattern. – Brenn Aug 07 '15 at 21:42
  • 4
    Somebody voted me down. So, let's double down on this. If you think this is not a race condition because it doesn't involve threads YOU ARE WRONG. Period. https://en.wikipedia.org/wiki/Race_condition You don't even need it to be software, it can happen with electrical circuitry. It's especially prevalent in multi-threaded code, but by no means does it REQUIRE multiple threads. – Brenn Aug 13 '15 at 17:38
  • 1
    No, there are no race conditions in JavaScript. Exploring the (non-existing) race conditions in JavaScript, and why they can never happen: https://blog.raananweber.com/2015/06/17/no-there-are-no-race-conditions-in-javascript/ – Davide Muzzarelli Jan 26 '17 at 20:42
  • 1
    @Rampant He is correct. Race conditions can **only** exist if you have multiple threads, which Javascript does not. You're confusing race condition with unknown callback execution order. That's as much of a race condition as providing two buttons with two click callbacks and not knowing which one the user will click on first. In other words, it's not. Please have a read again through the Wikipedia article you linked a fully understand the example provided: https://en.wikipedia.org/wiki/Race_condition#Example – Daniel T. Feb 14 '17 at 03:32
  • No. You are wrong and he is wrong. See the same article and the discussion of race conditions in electronic circuits. The exact situation where button pushing exists and where threads are an abstraction that doesn't even exist. – Brenn Feb 14 '17 at 07:34
  • It's well established in the JS community that race conditions exist. It's just not the conventional multi-threaded problem. Under certain conditions (that the developer might not be aware of), the same code might be resolved immediately or in the near future. Having a specific order tied to the execution of your code in these cases will lead to unexpected outcomes. Race condition. – zavjs Feb 25 '19 at 12:22