AJAX and Race Conditions
Thursday, 15th April 2010
Race conditions are a very common occurrence in the world of development. A race condition is essentially a scenario where two or more things happen simultaneously and have a potential clash. For example:
John gets a letter from a customer telling him about a change of address. He opens up the "Customer Details" page and starts typing in the new address. Meanwhile, the customer is on the phone to Bob, letting him know that his surname has been mis-spelled. Bob also opens the "Customer Details" page and starts correcting the surname.
We now have a race condition.
If Bob saves his information first, and then John saves - Bob's change will be overwritten by the data on John's screen, which is the new address, but the old surname. Equally, if John saves the information first and then Bob saves, John's change will be overwritten with the old address and the new surname.
This can also happen in code (in a great many scenarios) and in a specific case I recently helped to fix, in AJAX requests.
The problem being faced was a shopping cart solution using AJAX to add products to the basket. This is an increasingly common feature these days as it allows a product to be added to to the cart without updating the page.
In this particular case, a bundle was being added to the shopping cart, which consisted of two products. To facilitate this, two individual "add to basket" calls were being made using AJAX. When the basket was displayed, only one product appeared.
This is a classic example of a race condition.
In a normal "add to basket" operation, the control flow looks like this:
- Does a basket exist (Y/N) If not, create a new basket...
- Add product to basket
- Save basket
But as two "add to basket" operations were being called simultaneously, this was happening:
- (Product 1) Does a basket exist (Y/N) - NO - create a new basket (Basket 1)
- (Product 2) Does a basket exist (Y/N) - NO - create a new basket (Basket 2)
- (Product 1) Add product to basket
- (Product 2) Add product to basket
- (Product 1) Save basket
- (Product 2) Save basket
As the "current basket" is identified in a cookie, the LAST basket to be saved ends up being linked to the current shopping session and the FIRST basket to be saved is lost.
So how do you fix this problem?
In this instance, you can fix the problem in a couple of ways. You can chain the AJAX requests to ensure that each one is executed in sequence (in this example, the sequence doesn't actually matter - as long as both don't happen at once. In other cases, the exact order may be important). Another fix would be to create a mechanism that allows multiple products to be added to the basket in a single request (this is a better fix as it reduces the number of HTTP requests). So how do you chain a series of "add to basket" requests?
Here is a quick and simple jQuery solution that will work with a number of requests.
var ChainAjaxRequests = function (ajaxRequests, i) { if (i < ajaxRequests.length) { var uri = ajaxRequests[i]; i++; $.ajax({ url: uri, success: function(data) { // do something with "data" ChainAjaxRequests(ajaxRequests, i); }, error: function(xhr, ajaxOptions, thrownError) { alert(thrownError); } }); } }; var requests = new Array(); requests[0] = 'test1.html'; requests[1] = 'test2.html'; ChainAjaxRequests(requests, 0);