Wednesday, July 13th, 2005

Best practice for Asynchronous Javascript

Category: Editorial

Nick Lothian ran into issues with the A in Ajax:

One thing I see missing from most of the discussions about AJAX is implicit in its name: the calls are Asynchronous. This introduces the potential for some very nasty bugs when the state of the view is different to the state when the remote method was called. If (as is typically the case), the data returned from the call is used to modify the view all kinds of unusual problems ensure.

In my case, I was tracking the “current node” of my tree view in a javascript variable, set when the user clicked on it. The same onClick handler then made a call via DWR to the server to get data, and the function that was called when the data is returned added the data as tree nodes under the current node.

In 99% of cases this worked fine, until I suddenly discovered nodes appearing under the wrong parent. What had happened was that the server was under more load, there were more nodes in one particular branch and the data was taking longer to return than before. That gave me a chance to click on a different node (changing the currentNode variable), and when the data was returned it was added under the new currentNode.

Now (of course) that bug seems obvious, but I’ve never seen much discussion of this previously, so I suspect I’m not the only one who has been bitten by it.

And he talks about two solutions that he sees:

There are two solutions I know to this problem:

  1. Lock the view. This means having a flag variable that makes sure only one call is in progress at any time. It has the
    advantage of being very easy to code and debug, but the disadvantage of restricting the number of things that occur at once.
  2. Pass the view state to the remote method and return it along with the data.
    This means the the function that processes the returned data can
    reconstruct the correct view state when the data is available. This has
    the advantage of allowing multiple interactions
    to occur at the same time on the same screen, but makes coding much
    more difficult (for instance, is it always appropriate to reconstruct
    the old view state when the user has chosen to do something else?)

What tips and tricks do you have for dealing with good ‘ole asynchronicity?

Posted by Dion Almaer at 12:09 pm
12 Comments

+++--
3.3 rating from 6 votes

12 Comments »

Comments feed

Send the target to the JS function (not to the server), then pass the target to the callback routine when ready, something like:

webmethod(url,request,callback,target)

function webmethod(url,request,callback,target)
{
// new XMLHttpRequest
// yadda yadda
// on async response: callback(response,target)
}

Validation at the callback routine is necessary to determine the proper state of the target.

You get the idea ;-)

Comment by XULPower — July 13, 2005

Also, use a closure for a callback; they are thread-safe in javascript. (Apologies if this code comes out badly-formatted, I can’t figure out how to add line-breaks in the preview.)

// Request data
// Can be called repeatedly without waiting for async response
function requestData(…) {
var a,…; // State, accessible in closure
function handleCallback(req) {
// Use state variables to interpret the data returned
….
}

req.onreadystatechange = handleCallback;
req.open(“GET”, url, true);
req.send(null);
}

Comment by Chris Purcell — July 13, 2005

Asynchronous programming is HARD. The asynchronous nature of browser programming makes it an order of magnitude more complex than traditional servlet/cgi programming. This arises from the fact that “Ajax” is asynchronous at every code entry points: lifecycle events are asych, input events are asynch, and data retrievals are asynch. This differs greatly from server-side web-app programming, where only the “input events” are asynch. Having both input and data retrievals asynch makes it extremely difficult to follow code paths and track down bugs. If you encounter an error processing an RPC response, you can’t unwind the stack back to the input event that triggered the RPC.

I haven’t seen any frameworks that explicitly deal with the difficult of asych programming. There are some frameworks (such as Laszlo) that provide extensive data-binding capabilities. That’s at least a step in the right direction.

Comment by Neil Mix — July 13, 2005

I thought there was a parameter that would make the call synchronous, blocking any page handling until the request returned?

Comment by coderonin — July 13, 2005

Asynchronous sucks for the developer, but synchronous sucks for the user, so we have to suck it up and deal with these issues.

Comment by Adam Vandenberg — July 13, 2005

The problem of asynchronous access is a real pain for JavaScript not because it’s so hard to use multiple threads for developer but because there is no support for synchronization primitives in JavaScript.
You cannot even wait for some object which was locked by one call to become free.
Anyway some high-level synchronization tools are needed to make asynchronous programming really possible.

Comment by Roman Hawk — July 13, 2005

The ability to cancel requests is important as well. Sometimes, when the user changes the selection before the response comes back, you’ll just want to call request.abort() rather than allowing the response to be evaluated.

Comment by Joe — July 14, 2005

I am surprised that people are raising this issue now, as surely it is exactly the same as creating properly threaded Swing apps that deal with server communication. The problems and patterns for solution should be exactly the same, because while AJAX is something new for the web, it is not a programming paradigm shift. Just a thought…

Comment by Matt — July 14, 2005

The issue raised by Adam would only be a problem if Javascript were multithreaded. To my knowledge, the Javascript for a single page is executed in a single thread – the same thread that updates the GUI. An asynchronous request only calls back when all Javascript code stops running.

To demonstrate this, try entering an infinite loop after requesting asynchronous data transfer. The callback will never get called.

Only pre-emptive code needs high-level synchronization tools.

Comment by Chris Purcell — July 14, 2005

Dear Chris.
Blocking event-dispatching thread will lead to the situation you’ve outlined.
But try using setTimeout or setInterval functions. Their content can easily mess up with each other. Then add some asynchronous actions from AJAX and user and you’ll end up with perfect multithreaded application where bugs are raising in different places and in different circumstances.
Matt is absolutely right – asynchronous programming should look like Swing – but there is no sync tools. By the way, DWREngine seems to be good solution for strictly ordered requests.

Comment by Roman Hawk — July 14, 2005

Here is a good thread on this topic. The two authors of Ajax in Action gave some great input.
http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=20&t=005099

It deals with how to handle Ajax and client side validation, the async makes it difficult.

Comment by Terry Jeske — October 13, 2005

I ran into this problem and ended up using a closure to (i believe) safely handle multiple asynchronous requests (removed browser compat code for clarity). code follows…

function AjaxHandler( strHandler ) {
var request = null;
var handler = strHandler;
this.register = function( req ) {
request = req;
return this.change;
};
this.change = function() {
if ( request.readyState == 4 && request.status == 200 )
eval( handler + ‘( request )’ );
};

}

function xmlQuery( resourceURL, handler ) {
var req;
var ajax = new AjaxHandler( handler );
req = new XMLHttpRequest();
req.onreadystatechange = ajax.register(req);
req.open( ‘GET’, resourceURL, true );
req.send( null );
}

then to make a call just do…

xmlQuery( ‘file.xml’, ‘myHandler’ );

which is handled by the function…

function myHandler( request ) {}

which receives the request object used to make that particular ajax query.

Comment by rod — November 4, 2005

Leave a comment

You must be logged in to post a comment.