Synchronous v. Asynchronous

By YUI TeamApril 4th, 2006

XMLHttpRequest can operate synchronously or asynchronously. Many people prefer to use it synchronously. When used this way, the JavaScript engine is blocked until the interaction with the server is complete. Because it blocks, the flow of control looks a lot like an ordinary function invocation. Temporal complexity is abstracted away, leaving a very familiar and comfortable programming pattern. It works particularly well when the server is on the same machine, or nearby on the LAN. Unfortunately, it can perform very badly if the server is under heavy load, or if the browser is connected to the server over a slow link. Because the JavaScript engine is blocked until the request completes, the browser will be frozen. The user cannot cancel the request, cannot click away, cannot go to another tab. This is extremely bad behavior.

Fortunately, XMLHttpRequest provides an option for asynchronous operation. When you set the asyncFlag flag to true, the JavaScript engine does not block. Instead the request returns immediately, with a potential action that will be triggered later when the result on the request is known. The Yahoo! Connection Manager provides a very nice interface for this.

var cObj = YAHOO.util.Connect.asyncRequest('GET', 'http://myservice.com?req=update', {
    success: function (response) {
        alert(response.responseText);
    },
    failure: function (response) {
        alert(response.statusText);
    }
});

You supply two functions. Your success function contains everything that should happen as a result of the request succeeding. So if the request was to obtain some JSON text which should be delivered to the app.update method, then your success function could be

    success: function (response) {
        app.update(eval('(' + response.statusText + ')'));
    }

Asynchronous programming is slightly more complicated because the consequence of making a request is encapsulated in a function instead of following the request statement. But the realtime behavior that the user experiences can be significantly better because they will not see a sluggish server or sluggish network cause the browser to act as though it had crashed. Synchronous programming is disrespectful and should not be employed in applications which are used by people.

26 Comments

  1. But there’s one big problem with asynchronous XMLHttpRequest. What if there is more than one pending async call but the responses “return” in a different order than the requests were sent? It isn’t hard to think of an example. For instance, I may have a select for states which onselect send an async message to obtain a list of cities. A user may select “California”, then quickly select “Colorado”. However, the list of Colorado’s cities may return before California’s. Depending on the technique used to display the list of cities, the user may see California’s cities but hae Colorado selected.

    I know this can be handled to some degree by using various techniques to order responses (a sequence sent on a request returned by the response), queuing, etc… but I think these solutions only work if the state of the page is orthogonal to that returned by the response to the XMLHttpRequest…

  2. To Sync or not to Sync!…

    The Yahoo! UI Blog shares how to use XMLHttpRequest in a sort of pseudo-multithreaded and event-oriented fashion.  My favorite quote: “Synchronous programming is disrespectful and should not be employed in applications which are used by people.”…

  3. I often lamented back in the day that Javascript doesn’t have a non-blocking sleep() method. That’s all it would take to launch an async call and then wait for evidence of its completion before continuing from where you made the call without losing context.

    Quite a few early XMLHttp code implementations used a tight loop waiting for readystate==4, which caused similar browser lockups.

    But then I just sucked it up and learned to deal with asynchronous event-driven programming.

  4. Ted, theres 2 ways round this that I can think of:

    1. Disable the select when a user selects something, and diplay a “getting/loading” message. It remains disabled until the request returns.

    2. Id the request in the browser and send that id as part of the request, when the request returns it checks if the returned id matches the currently set id in the browser. If they dont match then it means your scenario has occurred and the results should not be displayed. If they do match then its ok to display the results.

  5. To deal with the async requests coming back in a different order you don’t even need to change the server so that it returns some request id. You can either wrap your XMLHttpRequest object with a custom class that has the id as a property on it. Then a reference to this wrapping class can be passed as a parameter to you success / failure method. If you really want you can even just set the id property right on the XMLHttpRequest object – that just makes it harder for programmers to understand :)

  6. brandon beacher said:
    April 25, 2006 at 9:08 pm

    I’ve used Dave’s technique with Math.random() as the generator of the request id. As the requests complete, I compare their ids to the most recent id, ignoring those which do not match.

    To get really crazy you could add a timer to front of the id approach. Using window.setInterval, wait X milliseconds before making the request. If a new request arrives during the interval you can drop the request. This adds the benefit that if the user makes several rapid UI changes it does not hammer the server.

  7. I understand the various ways to handle ordering issues with regards to async requests… but I think it’s a wrinkle that makes AJAX programming considerably trickier. It’s like going from code where you can assume a single thread to writing multi-threaded code. Suddenly, it seems like you have to have a different level of ability in the person writing the code…

  8. In the last example of the “success” function, shouldn’t it be response.responseText rather than response.statusText?
    Also, for failures, is there some sort of timeout? A cool feature would be the ability to set a timeout, like 10 seconds, after which the request automatically assumes a failure.

  9. Ted,

    Your point is valid. Some people discussed adding an ID attribute to requests. This is a decent solution, as long as you keep it in connection headers, as it really has no business being in the XML response body.

    Another solution is one I presented in an article on DevX about a year ago:

    http://www.devx.com/webdev/Article/28695

    In this case, your code doesn’t care about order or even the global structure of the XML response body. Handlers are “attached” to XML nodes and the handler is fired whenever that XML node is encountered in an XML response body. An example might be a node named chat-msg, which gets passed to a function called appendChatMsg(node) each time the node is encountered.

    This way, your app is not concerned with the communication that is happening. Instead, different components of your application respond to new relevant data when it arrives.

    Hope this helps.

  10. “But there’s one big problem with asynchronous XMLHttpRequest. What if there is more than one pending async call but the responses “return” in a different order than the requests were sent?”

    The problem is not just order in which responses return – you could also get requests arriving out of order at the server. If the requests correspond to something changing / being stored on the server and the sequence they arrive is used implicitly when making that change (e.g. appending messages to a chat file), you also have a problem – the server also needs to “think” about ordering. Tried to illustrate those problems here.

    Attaching IDs / sequence numbers to requests can help although it will generally be application specific and in the client to server direction, needs care (what if ID 3 and 5 arrive at the server but 4 somehow vanishes – what should the server do – how much state should it maintain to keep track of this?).

    Another approach is simply don’t let the client send more than one request at a time. To help mitigate performance issues, “boxcarring” multiple payloads into a single request can help.

  11. This is opinionated software, but I offer the opinion is wrong.

    The Yahoo UI libraries should, as a first order of business, abstract the supported base objects as the de facto implementations deliver them. These base functions support synchronous requests. If you do not like this, lobby the w3 and the browser vendors. They will also tell you that you are wrong coincidentally.

    To simply state that “synchronous programming is disrespectful” is cute but pointless and furthermore wrong. You do not know my use case.

    Now to use these libraries to implement a viable use case for synchronous coding I am forced to introduce laborious and error prone hacks. I will likely just use Dojo instead, its developers are not interested in judging my requirements.

  12. brad–

    Your use case is one where you’d sacrifice usability over making part of the code slightly easier?

    Yahoo! has every right to steer people in any direction they wish with THEIR libraries. The code is licensed in a manner such that you can trivially modify it, if you wish. The XMLHttpRequest object itself is a fairly minimal amount of code; something you could easily implement yourself if you don’t wish to use the YUI libraries.

    The level of arrogance involved in telling Y! what they should and shouldn’t be implementing in giving away a set of open source JS libraries is a little astounding.

  13. Javascript and Ajax Items for Discussion…

    Items n links Libs…

  14. [TRM-1108] Remove Synchronous Ajax calls from Seg SelectionDatasources.js…

    Synchronous Ajax calls lock the browser. If for some reason the request did not return, the only way to unlock the browser is to kill it from Task Manager. Sync calls are not considered good programming form. And dont take it from me, here is a blog o….

  15. Wow, Yahoo! Connection Manager looks like Prototype ;-)
    Actually I like prototype. Also Scriptaculous uses it.

  16. Dealing with multiple request (like the colorado example give) is really not terribly difficult.

    My solution was to track the XMLHttpRequest object in a variable (we’ll call it xhr).

    When an attempt to make a new Ajax call is made I check xhr to see if it is null. If it is not I call xhr.abort() to kill the request (now it will not return, out of order or otherwise). Then I set xhr = null and make the new request storing the new XMLHttpRequest object in xhr.

    var xhr = null;
    function MakeAjaxCall() {
    if (xhr !== null) {
    xhr.abort();
    xhr = null;
    }

    xhr = new XMLHttpRequest(/* proper settings */);
    /* more code to complete the XMLHttpRequest */
    }

    I also add a delay when making Ajax requests from a UI component. This gives the user a tiny moment to cycle through selects without my code making and aborting a lot of Ajax calls.

    var select_change_timeout = null;
    var reasonable_delay = 100; // or something reasonable
    var select_element = document.getElementById("my_select_element");
    select_element.onchange = function () {
    if (select_change_timeout !== null) {
    clearTimeout(select_change_timeout);
    select_change_timeout = null;
    }

    select_change_timeout = setTimeout(MakeAjaxCall, reasonable_delay);
    };

    Disclaimer: code samples are simplified and untested and may not work for you.

  17. [...] @ http://yuiblog.com/blog/2006/04/04/synchronous-v-asynchronous/ var addthis_pub = ‘codelinkers'; Back to Codelinkers your virtual Link Sharing [...]

  18. A valid use for a synchronous call: requests made onunload.

    For example, you have a database application with a list view and a record view. Records are locked when loaded and unlocked when unloaded. The locking can be done server-side, but the unlocking needs to be done via a JavaScript request.

    By the time the request is complete the page is gone. If there was a callback function, it’s gone. If the loading of the next page is affected by the lock status, there is no guarantee that the XMLHttpRequest is finished before it starts loading. You can’t just put the unlock function on the list page, or in the link, because you don’t know what the user is clicking on or if they might want to have two records open.

  19. Ted

    While you have a valid point, I disagree that current behavior of XMLHttpRequest is a problem or faulty design. A true async operation should return when the operation is finished. It shouldn’t have dependency on when the operation is invoked.

    Previous comments has shown that it is easy to cope with the async behavior to achieve your goal. But if XMLHttpRequest is designed to have response return in invocation order, it will be very difficult to achieve true async operation, therefore make it useless on many other use cases.

  20. Here’s my synchronous use case: Before I respond to any of my 50 possible ajax gestures that depend on the user being logged in, I need to verify that the user is, in fact, still logged in – that there hasn’t been a session time-out. If the session is timed out, and I no longer know who’s logged in, I need to redirect to a log-in page (or some such action); otherwise, I proceed to fulfill the request for the logged in user.

    I do this with a simple javascript call, separate from the ajax call – I don’t modify all my ajax request handlers to do their own checking for member login, and their own event handling (in the success function) if the member is not logged in.

    I currently have a home-grown synchronous ajax call to check for logged-in-ness that I use before initiating the specific ajax action. I was hoping to drop the home-grown stuff and just use YUI – but it appears I can’t, since YUI doesn’t support synchonous calls. Is there some better YUI way to handle this use case?

  21. [...] synchronous ajax calls? Problem solved, right? Unfortunately, it’s not that simple – from the YUI blog: XMLHttpRequest can operate synchronously or asynchronously. Many people prefer to use it [...]

  22. lol, Crockford speaks the truth, and 10 pages of complaints ensue.

    I wrote a bittorrent tracker in Python, using an asynchronous select/poll model ala lighttpd. It uses all the tricks in the book, including deferred execution of closures, deferred execution of SQL queries via thread pool, generators as asynchronous fake threads, deferred zero-overhead firewall checks, and it can reload its own code without restarting. Number of concurrently opened sockets is limited only by the OS and it could handle about 1000 HTTP hits per second on my old pentium-m laptop. Since then (about 2 years ago) it has never crashed. This code could be called complex. Not “very” complex though.

    Anyway back to xmlhttp :

    var seq_counter = a global sequence counter.
    var pending = an array of pending xmlhttp requests.

    When sending a request, tag it with seq_counter, and increment seq_counter. Store the request in the “pending” array. Perhaps init a timeout to kill it if it takes too long, that’s up to you.

    When you get a response, look in the pending requests array. Scan it in order of sequence number.

    Process all responses received, popping them out of the array, until you find an unanswered request. If this happens, stop processing. The processing will resume from this point when/if the response is received, or the timeout kills it.

    This is pretty simple and it guarantees that all responses will be processed in the same order as the requests.

  23. Very simple code to illustrate my rant above.
    Automatically reorders xmlhttp responses.
    Put files on some server with PHP and go there.
    Have a nice day ;)

    http://home.peufeu.com/posts/xhreorder.rar

    Oooooo by the way it only works in Opera cause that’s what I had on hand…

  24. [...] method instead of the preferred asynchronous method for web application development. This blog entry from the Yahoo! User Interface Blog makes this very clear, and it is even more so for mobile web [...]