Making callbacks (and Atlas) synchronous, or how to shoot yourself in the foot
I've explained before why XmlHttpRequest should always be used asynchronously. In a nutshell, JavaScript is not multi-threaded, so the only way to keep your application and browser reasonably responsive is to use some kind of asynchronous pattern. This way, the multitasking is left to the hosting browser and the JavaScript developer can enjoy a relatively easier programming environment where he only needs to care about events and not about summoning threads and managing locks.
It's important to note that if you click on a link in a browser, it usually doesn't freeze: the UI is still fully usable even while the request is being completed. You can still cancel it by pressing the stop button, you can access all the menus, etc.
While there is a synchronous XmlHttp request going on, it's a different matter: the browser is completely frozen and none of the UI works. This is utterly wrong on several accounts.
First, if the server never answers, your users will need to kill the browser (assuming they know how to do that, which they usually don't).
Second, any UI that freezes for more than half a second without giving the user a clue about what's going on (remember the little animation that usually indicates navigation or posting back does not move during an XmlHttp request), as far as the user is concerned, just looks as if it had crashed.
Finally, the web application should not have side effects on its container (the browser). In particular, it should not put it into an unresponsive state. I agree that the browser should not let itself be frozen by its contents, but that's unfortunately the way it is and we just have to deal with it (by the way, Firefox reacts exactly the same way as IE in this department).
That's why callbacks in both ASP.NET 2.0 and Atlas are always asynchronous. The async parameter in the case of ASP.NET callbacks is mileading. It should really be named "parallel": when set to true, any number of callbacks may be initiated simultaneously and if false, only the last initiated will actually call back.
That being said, I've been getting a lot of feedback lately from people who just dislike so much asynchronous programming that they want nothing to do with it (even if it's conveniently hidden from them like it is in Atlas). Well, if what you really want is to shoot yourself in the foot, who am I to argue with that? You're the customer, and I'm here to answer your demands. So here's the gun... (of course I'm kidding here. I understand why people want to use synchronous callbacks even if I personally disagree).
Add this small script to your page (preferably in the <head> section) and all your XmlHttp requests will be done synchronously no matter what the framework you're using is doing. This works with ASP.NET 2.0 callbacks in both IE and Firefox but breaks callbacks for Opera. I suspect that it would also work with other Ajax frameworks such as Atlas.
<script type="text/javascript">
var __xmlHttpRequest = window.XMLHttpRequest;
window.XMLHttpRequest = XMLHttpRequest = function() {
var _xmlHttp = null;
if (!__xmlHttpRequest) {
try {
_xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(ex) {}
}
else {
_xmlHttp = new __xmlHttpRequest();
}
if (!_xmlHttp) return null;
this.abort = function() {return _xmlHttp.abort();}
this.getAllResponseHeaders = function() {return _xmlHttp.getAllResponseHeaders();}
this.getResponseHeader = function(header) {return _xmlHttp.getResponseHeader(header);}
this.open = function(method, url, async, user, password) {
return _xmlHttp.open(method, url, false, user, password);
}
this.send = function(body) {
_xmlHttp.send(body);
this.readyState = _xmlHttp.readyState;
this.responseBody = _xmlHttp.responseBody;
this.responseStream = _xmlHttp.responseStream;
this.responseText = _xmlHttp.responseText;
this.responseXML = _xmlHttp.responseXML;
this.status = _xmlHttp.status;
this.statusText = _xmlHttp.statusText;
this.onreadystatechange();
}
this.setRequestHeader = function(name, value) {return _xmlHttp.setRequestHeader(name, value);}
}
</script>
Update: modified the script to include more properties of the XHR object, which makes the script compatible with ASP.NET 3.5.