2.3 Sending Asynchronous Requests
Synchronous requests are easier to use than asynchronous requests because they return data directly and remove the hassle of creating callback functions. However, they aren't the standard use case for XMLHttpRequest because the entire browser is locked while the request is happening. There are some circumstances in which blocking is useful (mainly when a decision needs to be made before the current function ends), but in most cases, you'll want these requests to happen in the background. An asynchronous request allows the browser to continue running JavaScript code and users to continue interacting with the page while the new data is loaded. With the proper user interface, asynchronous communication allows an AJAX application to be useful even when the user's connection to the site is slow.
To make an asynchronous call, we need to accomplish two tasks: set the asynchronous flag on open to true, and add a readyStateChanged event handler. This event handler will wait for a ready state of 4, which means the response is loaded. It will then check the status property. If the status is 200, we can use responseText; if it's another value, we have an error, so we'll need to create an alert dialog to show it. An asynchronous call to test.php is shown in Listing 2-2. The initXMLHttpClient function from an earlier chapter section, "Cross-Browser XMLHttpRequest," is used to create our XMLHttpRequest object.
Listing 2-2. Making an Asynchronous Request
1 var req = initXMLHttpClient(); 2 req.onreadystatechange = function() { 3 if (req.readyState == 4) { 4 if (req.status == 200) { 5 alert(req.responseText); 6 } else { 7 alert('Loading Error: ['+req.status+'] ' 8 +req.statusText); 9 } 10 } 11 } 12 req.open('GET','test.php',true); 13 req.send(null);
Although this code gets the job done, it's not a great long-term solution because we will have to write a new onreadystatechange method for each call. The solution to this is to create our own HttpClient class that wraps XMLHttpRequest. Such a class gives us an easy-to-use API and a property to use for the callback that has to deal only with successful requests. Just adding some helper methods would be a simpler solution, but that's not a possibility because IE doesn't allow you to add methods to an ActiveX object.
A sample XMLHttpRequest wrapper class is shown in Listing 2-3. The main features of the HttpClient class are a callback property that is called when a successful asynchronous request is complete and a makeRequest method that combines the open and send functions. It also provides event properties that are called when a request is made (onSend), when it ends (onload), and when an errors occurs (onError). A default onSend and onLoad implementation is provided, which creates a basic loading message while requests are being made.
Listing 2-3. HttpClient XMLHttpRequest Wrapper
1 function HttpClient() { } 2 HttpClient.prototype = { 3 // type GET,POST passed to open 4 requestType:'GET', 5 // when set to true, async calls are made 6 isAsync:false, 7 8 // where an XMLHttpRequest instance is stored 9 xmlhttp:false, 10 11 // what is called when a successful async call is made 12 callback:false, 13 14 // what is called when send is called on XMLHttpRequest 15 // set your own function to onSend to have a custom loading 16 // effect onSend:function() { 17 document.getElementById('HttpClientStatus').style.display = 18 'block'; 19 }, 20 21 // what is called when readyState 4 is reached, this is 22 // called before your callback 23 onload:function() { 24 document.getElementById('HttpClientStatus').style.display = 25 'none'; 26 }, 27 28 // what is called when an http error happens 29 onError:function(error) { 30 alert(error); 31 }, 32 33 // method to initialize an xmlhttpclient 34 init:function() { 35 try { 36 // Mozilla / Safari 37 this.xmlhttp = new XMLHttpRequest(); 38 } catch (e) { 39 // IE 40 var XMLHTTP_IDS = new Array('MSXML2. XMLHTTP.5.0', 41 'MSXML2.XMLHTTP.4.0', 42 'MSXML2.XMLHTTP.3.0', 43 'MSXML2.XMLHTTP', 44 'Microsoft.XMLHTTP'); 45 var success = false; 46 for (var i=0;i < XMLHTTP_IDS.length && !success; i++) { 47 try { 48 this.xmlhttp = new ActiveXObject (XMLHTTP_IDS[i]); 49 success = true; 50 } catch (e) {} 51 } 52 if (!success) { 53 this.onError('Unable to create XMLHttpRequest.'); 54 } 55 } 56 }, 57 58 // method to make a page request 59 // @param string url The page to make the request to 60 // @param string payload What you're sending if this is a POST 61 // request 62 makeRequest: function(url,payload) { 63 if (!this.xmlhttp) { 64 this.init(); 65 } 66 this.xmlhttp.open(this.requestType,url,this.isAsync); 67 68 // set onreadystatechange here since it will be reset after a 69 //completed call in Mozilla 70 var self = this; 71 this.xmlhttp.onreadystatechange = function() { 72 self._readyStateChangeCallback(); } 73 74 this.xmlhttp.send(payload); 75 76 if (!this.isAsync) { 77 return this.xmlhttp.responseText; 78 } 79 }, 80 81 // internal method used to handle ready state changes 82 _readyStateChangeCallback:function() { 83 switch(this.xmlhttp.readyState) { 84 case 2: 85 this.onSend(); 86 break; 87 case 4: 88 this.onload(); 89 if (this.xmlhttp.status == 200) { 90 this.callback(this.xmlhttp.responseText); 91 } else { 92 this.onError('HTTP Error Making Request: '+ 93 '['+this.xmlhttp. status+']'+ 94 '+this.xmlhttp. statusText)); 95 } 96 break; 97 } 98 } 99 }
The HttpClient class contains comments explaining its basic functionality, but you will want to look at a couple of areas in detail. The first areas are the properties you'll want to set while interacting with the class; these include the following:
- requestType (line 4). Used to set the HTTP request type, GET is used to request content that doesn't perform an action whereas POST is used for requests that do.
- isAsync (line 6). A Boolean value used to set the request method. The default is false, which makes an synchronous request. If you're making an asynchronous request, isAsync is set to true. When making an asynchronous request, you also need to set the callback property.
- callback (line 12). This property takes a function that takes a single parameter result and is called when a request is successfully completed.
Lines 16–31 contain simple functions for handling some basic user feedback. When a request is sent to the server, a DOM element with the ID of HttpClientStatus is shown (lines 16–19). When it completes, it is hidden again (lines 23–26). The class also defines a function to call when an error happens (lines 29–31); it creates an alert box with the error message. Common errors include receiving a 404 page not found HTTP error message or not being able to create an XMLHttpRequest object. The implementation of these three functions is simple, and you'll likely want to override them with more sophisticated application-specific versions.
Lines 33–56 contain the init method, which is identical to the initXMLHttpClient function we created in Listing 2-1, except for what it does with its error message. Now it sends it to the onError method. You won't be dealing with this function directly because the makeRequest method will take care of it for you. The makeRequest method (lines 62–79) is your main interaction with the class. It takes two parameters: a URL to which to make the request and a payload that is sent to the server if you're making a POST request. The actual implementation is a more generic version of the code shown in Listing 2-2. The _readyStateChangeCallback (lines 82–99) method is set as the readyState handler by makeRequest. It handles calling onSend when the initial request is sent and then calling onload when the request completes. It also checks for a 200 HTTP status code and calls onError if some other status is returned.
Listing 2-4 uses the HttpClient class and shows its basic usage. A wrapper class like this helps cut down the amount of code you need to write per request while giving a single place to make future changes.
Listing 2-4. Using the HttpClient XMLHttpRequest Wrapper
1 <html> 2 <head> 3 <title>Simple XMLHttpRequest Wrapper Test Page</title> 4 5 <script type="text/javascript" src="HttpClient.js"></script> 6 <body> 7 <script type="text/javascript"> 8 9 var client = new HttpClient(); 10 client.isAsync = true; 11 12 function test() { 13 client.callback = function(result) { 14 document.getElementById('target').innerHTML = result; 15 } 16 client.makeRequest('.',null); 17 } 18 </script> 19 20 <div id="HttpClientStatus" style="display:none">Loading ...</div> 21 <a href='javascript:test()'>Make an Async Test call</a> 22 <div id="target"></div> 23 </body> 24 </html>
Using the HttpClient XMLHttpRequest wrapper is a simple task. You start by including it in the header of your HTML page (line 5), and then you can proceed to use it. You do this by creating an instance of the class (line 9), configuring its basic properties (in this case, setting isAsync to true (line 10)), and then setting up some code to call makeRequest. In most cases, this code will be contained in a function so that it can be tied to a user-driven event, such as clicking a link. The call is made by the test function (lines 12–17); the test function first sets up a callback to run when the request is complete (lines 13–15), and then it calls makeRequest (line 16), which starts the AJAX call.