Unique and impressive
Creativity and Functionality
Featured and scalable
Impressive and interactive
Represents your brand values
www.multiframes.com

Mastering Ajax: Advanced requests and responses in Ajax


 


Level: Intermediate

For many web developers, making simple requests and receiving simple responses is all that will ever be needed. However, for developers that want to truly master Ajax, a complete understanding of HTTP status codes, ready states, and the XMLHttpRequest object is required. In this article, you will learn about the different status codes, and how browsers handle them differently, as well as about the lesser-used HTTP requests that you can make with Ajax.

In the last article in this series (check the Resources section for links), you received a solid introduction to the XMLHttpRequest object. XMLHttpRequest is really the centerpiece of an Ajax application, handling requests to a server-side application or script, as well as dealing with return data from that server-side component. Every Ajax application uses the XMLHttpRequest object, and you'll need to be intimately familiar with it to make your Ajax applications perform, and perform well.

In this article, we're going to move beyond the basics you encountered in the last article. Specifically, you'll learn more about three key parts of this request object:

  1. The HTTP ready state
  2. The HTTP status code
  3. The types of requests you can make

Each of these is generally considered part of the "plumbing" of a request; as a result, there is often little written or said about any of these subjects, particularly in any detail. However, you'll need to be fluent in ready states, status codes, and requests if you want to do anything more than dabble in Ajax programming. The first time that something goes wrong in your application -- and things always go wrong, as any experienced programmer realizes -- understanding ready states, or how to make a HEAD request, or what a 400 status code means, can make the difference between 5 minutes of debugging, and 5 hours of frustration and confusion.

XMLHttpRequest or XMLHTTP

You should recall from the last article that Microsoft and Internet Explorer use an object called XMLHttp instead of the XMLHttpRequest object used by Mozilla, Opera, Safari, and most non-Microsoft browsers. For the sake of simplicity, I refer to both of these object types simply as XMLHttpRequest. This matches the common practice you'll find all over the Web, and also is inline with Microsoft's intentions of using XMLHttpRequest as the name of their request object in Internet Explorer 7.0.

Digging Deeper into HTTP Ready States

You should remember from the last article that the XMLHttpRequest object has a property called readyState. This property is used to ensure that a request has been completed by a server; then, typically, a callback function uses the data from the server to update a web form or page. Listing 1 shows a simple example of this (also from the last article in this series).

 

 


Listing 1. Dealing with a server's response in a callback function



   function updatePage() {
     if (request.readyState == 4) {
       if (request.status == 200) {
         var response = request.responseText.split("|");
         document.getElementById("order").value = response[0];
         document.getElementById("address").innerHTML =
           response[1].replace(/\n/g, "
"); } else alert("status is " + request.status); } }

This is definitely the most common -- and simplest -- usage of ready states. As you might guess from the number "4", though, there are several other ready states (you saw this list in the last article, as well):

  • 0: The request is uninitialized (before you've called open()).
  • 1: The request is setup, but hasn't been sent (before you've called send()).
  • 2: The request has been sent, and is being processed (you can usually get content headers from the response at this point).
  • 3: The request is being processed; there's often some partial data available from the response, but the server hasn't finished with its response.
  • 4: The response is complete; you can get the server's response and use it.

If you want to go beyond the basics of Ajax programming, you need to not only know about these states, but when they occur, and how you can use them. First and foremost, you need to learn at what state of a request you'll encounter each ready state. That turns out to be fairly non-intuitive, and also involves a few special cases.

Ready States in Hiding

The first ready state, signified by the readyState property being "0", represents an uninitialized request. As soon as you call open() on your request object, this property will be set to "1", though. Since you'll almost always call open() as soon as you initialize your request, it's rare to see a ready state of "0". Further, the uninitialized ready state is pretty useless in a practical applications. Still, in the interest of being complete, check out Listing 2, which shows how to get the ready state when it's set to "0".


Listing 2. Getting a "0" ready state


   function getSalesData() {
     // Create a request object
     createRequest();		
     alert("Ready state is: " + request.readyState);

     // Setup (initialize) the request
     var url = "/boards/servlet/UpdateBoardSales";
     request.open("GET", url, true);
     request.onreadystatechange = updatePage;
     request.send(null);
   }

In this simple example, getSalesData() is the function that your web page would call to start a request, for instance when a button is clicked. Note that you've got to check the ready state before open() is called here. The result of running this application is shown in Figure 1.


Figure 1. A ready state of 0
Checking the ready state before calling open() results in a 0 ready state
When 0 is equal to 4

In the use case described in this section -- where multiple JavaScript functions are using the same request object -- checking for a ready state of "0" to ensure that the request object isn't in use can still turn out to be problematic. Since the ready state of "4" indicates a completed request, you'll often find request objects that are not being used sitting around with their ready state still set at "4"; the data from the server was used, but nothing has occurred since then to reset the ready state. There is a function that resets a request object called abort(), but it's not really intended for this use. If you have to use multiple functions, it might be better to simply create and use a request object for each function, rather than dealing with trying to share the object across multiple functions.

Obviously, this doesn't do you much good; there are very few times when you'll need to make sure that open() hasn't been called. The only use for this ready state in "real world" Ajax programming is if you are making multiple requests using the same XMLHttpRequest object, across multiple functions. In that (rather unusual) situation, you might want to ensure that a request object is in an uninitialized state (and that the ready state is "0") before making a new requests; this essentially ensures that there's not another function using the object at the same time.

Viewing an In Progress Request's Ready State

Aside from the "0" ready state, your request object should go through each of the other ready states in a typical request and response, finally ending up with a ready state of 4. That's when the if (request.readyState == 4) line of code you see in most callback functions comes in; it ensures the server is done, and it's safe to update a web page or take action based on data from the server.

To actually see this process, as it takes place, is trivial. Instead of only running code in your callback if the ready state is "4", just output the ready state every time that your callback is called. For an example of code that does this, check out Listing 3.


Listing 3. Checking the ready state.


   function updatePage() {
     // Output the current ready state
     alert("updatePage() called with ready state of " + request.readyState);
   }

If you're not sure how to get this running, you'll need to create a function to call from your web page, and have it send a request to a server-side component; just such a function was shown back in Listing 2 (and throughout the examples in both the first and second articles in this series). Just make sure that when you set up your request, you set the callback function to "updatePage"; you can do this by setting the onreadystatechange property of your request object to updatePage.

This code is a great illustration of exactly what onreadystatechange means; every time the request's ready state changes, updatePage() is called, and you'll get an alert. Figure 2 shows a sample of this function being called, in this case with a ready state of "1".


Figure 2. A ready state of 1
Checking the ready state after calling open() results in a 1 ready state

Try this code yourself; put it into your web page, and then activate your event handler (by clicking a button, tabbing out of a field, or using whatever method you've setup to trigger a request). Your callback function will run several times -- each time the ready state of the request changes -- and you'll see an alert for each ready state. There's no better way to follow a request through each of its stages.

Browser Inconsistencies

Once you've got a basic understanding of this process, try accessing your web page from several different browsers. You should notice some inconsistencies in how these ready states are handled. For example, using FireFox 1.5, you'll get the following ready states:

  1. 1
  2. 2
  3. 3
  4. 4

This shouldn't be any surprise, as each stage of a request is represented here. However, if you access the same application using Safari, you should see -- or rather, not see -- something interesting. Here are the states you'll see on Safari 2.0.1:

  1. 2
  2. 3
  3. 4

Safari actually leaves out the first ready state, and there's not any sensible explanation as to why this occurs. However, it's simply the way Safari works. It also points out an important point: while it's a good idea to ensure the ready state of a request is "4" before using data from the server, writing code that depends on each interim ready state -- "1", "2", and "3" -- is a sure way to get different results on different browsers. For example, using Opera 8.5, things get even worse; here are the ready states shown there:

  1. 3
  2. 4

Last but not least, Internet Explorer responds with the following states:

  1. 1
  2. 2
  3. 3
  4. 4

If you're having trouble with a request, this is the very first place to start looking for problems; add an alert to show you the request's ready state, and ensure that things are operating normally. Better yet, test on both Internet Explorer and Firefox, as you'll get all four ready states, and be able to check each stage of the request.


Back to top


Response Data under the Microscope

Once you understand the various ready states that occur during a request, you're ready to look at another important piece of the XMLHttpRequest object: the responseText property. You should recall from the last article that this is the property used to get data from the server. Once a server has finished processing a request, it places any data that is needed to respond to the request back in the responseText of the request. Then, your callback function can use that data, as seen in Listing 1 (shown earlier) and Listing 4 (below).


Listing 4. Using the response from the server


   function updatePage() {
     if (request.readyState == 4) {
       var newTotal = request.responseText;
       var totalSoldEl = document.getElementById("total-sold");
       var netProfitEl = document.getElementById("net-profit");
       replaceText(totalSoldEl, newTotal);

       /* Figure out the new net profit */
       var boardCostEl = document.getElementById("board-cost");
       var boardCost = getText(boardCostEl);
       var manCostEl = document.getElementById("man-cost");
       var manCost = getText(manCostEl);
       var profitPerBoard = boardCost - manCost;
       var netProfit = profitPerBoard * newTotal;

       /* Update the net profit on the sales form */
       netProfit = Math.round(netProfit * 100) / 100;
       replaceText(netProfitEl, netProfit);
     }

Listing 1 is fairly simple, and Listing 4 is a little more complicated, but both begin by checking the ready state, and then grabbing the value (or values) in the responseText property.

Viewing the response text during a request

Like the ready state, the value of the responseText property changes throughout the request's lifecycle. To see this in action, use code like that shown in Listing 5 to test the response text of a request, as well as its ready state.


Listing 5. Testing the responseText property


   function updatePage() {
     // Output the current ready state
     alert("updatePage() called with ready state of " + request.readyState +
           " and a response text of '" + request.responseText + "'");
     }

Now open your web application in a browser and activate your request. To get the most out of this code, you should use either FireFox or Internet Explorer, as those two browsers both report all possible ready states during a request. At ready state 2, for example, Figure 3 shows that the responseText property is undefined (you'd see an error if the JavaScript console was open, as well).


Figure 3. Response Text with a Ready State of 2
The server usually hasn't given the request any data in a ready state of 2

At ready state 3, though -- at least in this example -- the server has placed a value in the responseText property (see Figure 4).


Figure 4. Response Text with a Ready State of 3
At ready state 3, many requests will have some data from the server

You'll find that your responses in ready state 3 vary, from script to script, server to server, and browser to browser. However, this is still incredibly helpful in debugging your application.

Getting Safe Data

All of the documentation and specifications insist that it's only when the ready state is 4 that data is safe to use. However, you'll rarely find a case where the data cannot be obtained from the responseText property, when the ready state is 3, is not a valid response. However, counting on that in your application is a bad idea; the one time you write code that depends on data being complete at ready state 3 is almost guaranteed to be the time the data is incomplete.

A better idea is to provide some feedback to the user, when the ready state is 3, that a response is forthcoming. While using a function like alert() is obviously a bad idea -- using Ajax and then blocking the user with an alert dialog box is pretty counterintuitive -- you could update a field on your form or page as the ready state changes. For example, try setting the width of a progress indicator to 25% for a ready state of 1, 50% for a ready state of 2, 75% for a ready state of 3, and 100% (complete) when the ready state is 4.

Of course, as you've seen, this approach is clever but browser-dependent. On Opera, you'll never get those first two ready states, and Safari drops the first one ("1"). For that reason, I'll leave code like this as an exercise, rather than include it in this article.



Back to top


A Closer Look at HTTP Status Codes

With status codes and the server's response in your bag of Ajax programming techniques, you're ready to add another level of sophistication to your Ajax applications: working with HTTP status codes. These codes are nothing new to Ajax; they've been around on the Web for as long as there has been a Web. You've probably already seen several of these through your web browser:

  • 401: Unauthorized
  • 403: Forbidden
  • 404: Not Found

There are a lot more of these; a complete list is available through the links in the Resources section. If you want to add another layer of control and responsiveness -- and particularly more robust error-handling -- to your Ajax applications, then you need to check status codes in a request, and respond appropriately.

200: Everything is OK

In many Ajax applications, you'll see a callback function that checks for a ready state, and then goes on to work with the data the server responded with, as in Listing 6.


Listing 6. Callback function that ignores the status code


   function updatePage() {
     if (request.readyState == 4) {
       var response = request.responseText.split("|");
       document.getElementById("order").value = response[0];
       document.getElementById("address").innerHTML =
         response[1].replace(/\n/g, "
"); } }

This turns out to be a short-sighted and error-prone approach to Ajax programming. If a script requires authentication, for example, and your request does not provide valid credentials, the server will return an error code (like 403 or 401). However, the ready state will be set to 4, as the server answered the request (even if the answer wasn't what you wanted or expected for your request). As a result, the user is not going to get valid data, and may even get a nasty error when your JavaScript tries to use server data that isn't there.

It takes minimal effort to ensure that the server not only finished with a request, but returned an "Everything is OK" status code. That code is "200", and is reported through the status property of the XMLHttpRequest object. To ensure that not only did the server finish with a request, but that it also reported an OK status, add an additional check in your callback function, as shown in Listing 7.


Listing 7. Checking for a valid status code


   function updatePage() {
     if (request.readyState == 4) {
       if (request.status == 200) {
         var response = request.responseText.split("|");
         document.getElementById("order").value = response[0];
         document.getElementById("address").innerHTML =
           response[1].replace(/\n/g, "
"); } else alert("status is " + request.status); } }

By adding a few lines of code, you ensure that if something does go wrong, your users will get a (questionably) helpful error message, rather than seeing a page of garbled data with no explanation.

Redirection and Rerouting

Before talking in depth about errors, it's wroth talking about something you probably don't have to worry about when you're using Ajax: redirections. When talking about HTTP status code, this is the 3xx family of status codes, including:

  • 301: Moved permanently
  • 302: Found (the request was redirected to another URL/URI)
  • 305: Use Proxy (the request must use a proxy to access the resource requested)

There are two reasons why this probably isn't a concern to Ajax programmers. First, Ajax applications are almost always written for a specific server-side script, servlet, or application. For that component to "disappear" and move somewhere else without you, the Ajax programmer, knowing about it is pretty rare. So more often than not, you'll know that a resource has moved, change the URL in your request, and never encounter this sort of result.

Probably even more relevant is the second reason this is rarely a problem: Ajax applications and requests are sandboxed. This means that the domain that serves a web page that makes Ajax requests is the domain that must respond to those requests. So a web page served from ebay.com cannot make an Ajax-style request to a script running on amazon.com; Ajax applications on ibm.com cannot make requests to servlets running on netbeans.org. As a result, your requests aren't able to be redirected to another server without generating a security error. In those cases, you won't get a status code at all; you'll usually just have a JavaScript error in the debug console. So, while there are plenty of status codes to worry about, you can largely ignore the redirection codes altogether.

Edge Cases and Hard Cases

At this point, novice programmers may wonder what all this fuss is about. It's certainly true that working with ready states like "2" and "3", and status codes like "403", probably represents less than 5% of Ajax requests (and in fact may be much closer to 1% or less). However, these are the edge cases; the situations that occur in very unusual situations, where the oddest conditions are met. While these are unusual, edge cases make up about 80% of most users' frustrations!

Typical users forget the 100 times an application worked correctly, but clearly remember the 1 time it didn't. If you can handle the edge cases -- and hard cases -- smoothly, then you'll have content users that return to your site.

Errors

Once you've taken care of status code 200, and realized you can largely ignore the 300 family of status codes, the only other group of codes to worry about is the 400 family, indicating various types of errors. Look back at Listing 7, and notice that while errors are handled, it's only a very generic error message that is output to the user. While this is a step in the right direction, it's still a pretty useless message in terms of telling the user -- or a programmer working on the application -- what actually went wrong.

First, add support for missing pages. This really shouldn't happen much in production systems, but it's not uncommon in testing for a script to move, or for a programmer to enter an incorrect URL. If you can report 404 errors gracefully, you're going to provide a lot more help to confused users and programmers. For example, if a script on the server was removed, and the code shown in Listing 7 were used, you'd get a pretty non-descript error like you see in Figure 5.


Figure 5. Generic error handling
No matter what error is encountered, a very generic message is sent to the screen

There's no way to tell if the problem is authentication, a missing script (which is the case here), user error, or even something in the code caused the problem. Some simple code additions can make this error a lot more specific, though. Take a look at Listing 8, which handles missing scripts as well as authentication errors with a specific message.


Listing 7. Checking for a valid status code


   function updatePage() {
     if (request.readyState == 4) {
       if (request.status == 200) {
         var response = request.responseText.split("|");
         document.getElementById("order").value = response[0];
         document.getElementById("address").innerHTML =
           response[1].replace(/\n/g, "
"); } else if (request.status == 404) { alert ("Requested URL is not found."); } else if (request.status == 403) { alert("Access denied."); } else alert("status is " + request.status); } }

This is still rather simple, but it does provide some additional information. Figure 6 shows the same error as in Figure 5, but this time the error-handling code gives a much better picture of what happened to the user or programmer.


Figure 6. Specific error handling
Now missing scripts result in a very specific error message being output

In your own applications, you might consider clearing the username and password when failure occurs because of authentication, and adding an error message to the screen. Similar approaches can be taken to more gracefully handle missing scripts, or other 4xx errors like 405 (for an unacceptable request method, like sending a HEAD request that is not allowed) or 407 (proxy authentication is required). Whatever choices you make, though, begins with handling the status code returned from the server.



Back to top


Additional Request Types

There's one last stop on really taking control of the XMLHttpRequest object, and that's adding HEAD requests to your repertoire. In the last two articles, you saw how to make GET requests; in an upcoming article, you'll learn all about sending data to the server using POST requests. In the spirit of enhanced error handling and information gathering, though, you should learn how to make HEAD requests.

Making the request

Actually making a HEAD request is quite trivial; you simply call the open() method with "HEAD" instead of "GET" or "POST" as the first parameter, as shown in Listing 8.


Listing 8. Making a HEAD request with Ajax


   function getSalesData() {
     createRequest();
     var url = "/boards/servlet/UpdateBoardSales";
     request.open("HEAD", url, true);
     request.onreadystatechange = updatePage;
     request.send(null);
   }

When you make a HEAD request like this, the server doesn't return an actual response like it would for a GET or POST request. Instead, the server only has to return the headers of the resource, which include the last time the content in the response was modified, whether the requested resource exists, and quite a few other interesting informational bits. You can use several of these to find out about a resource before the server has to process and return that resource.

The easiest thing you can do with a request like this is to simply spit out all of the response headers. This gives you a feel for what's available to you via HEAD requests. Listing 9 provides a simple callback function to output all the response headers from a HEAD request.


Listing 9. Printing all the response headers from a HEAD request


   function updatePage() {
     if (request.readyState == 4) {
       alert(request.getAllResponseHeaders());
     }
   }

Check out Figure 7, which shows the response headers from a simple Ajax application making a HEAD request to a server.


Figure 7. Response headers from a HEAD request
The server reports the server type, the connection type, the length of the response, and a lot more

Any of these headers -- from the server type to the content type -- can be used individually to provide extra information or functionality within an Ajax application.

Checking for a URL

You've already seen how to check for a 404 error, when a URL doesn't exist. If this turns into a common problem -- perhaps a certain script or servlet is offline quite a bit -- you might want to check for the URL before making a full GET or POST request. This is possible by making a HEAD request, and then checking for a 404 error in your callback function; a sample callback is shown in Listing 10.


Listing 10. Checking to see if a URL exists


   function updatePage() {
     if (request.readyState == 4) {
       if (request.status == 200) {
         alert("URL exists");
       } else if (request.status == 404) {
         alert("URL does not exist.");
       } else {
         alert("Status is: " + request.status);
       }
     }
   }

To be honest, there's little value in this. The server has to respond to the request and figure out a response, to populate the content-length response header, so you're not saving any processing time. Additionally, it takes just as much time to make the request and see if the URL exists using a HEAD request as it does to make the request using GET or POST, and then just handling errors as shown in Listing 7. Still, sometimes it's useful to know exactly what is available; you never know when creativity will strike and you'll need the HEAD request!

Useful HEAD requests

One area where a HEAD request can be useful is in checking the content length, or even the content type. This allows you to determine if a huge amount of data would be sent back to process a request, or if the server is going to try and return binary data, instead of HTML, text, or XML (which are all three much easier to process in JavaScript than binary data). In these cases, you just use the appropriate header name and pass it to the getResponseHeader() method on the XMLHttpRequest object. So to get the length of a response, just call request.getResponseHeader("Content-Length");; to get the content type, use request.getResponseHeader("Content-Type");.

In many applications, making HEAD requests won't add any functionality, and may even slow down a request (by forcing a HEAD request to get data about the response, and then a subsequent GET or POST request to actually get the response). However, in the event that you are unsure about a script or server-side component, a HEAD request can allow you to get some basic data without having to deal with response data, or the bandwidth needed to send that response.



Back to top


In conclusion

For many Ajax and web programmers, the material in this article will seem fairly advanced, and even trivial. What value is there in making a HEAD request? Is there really a case where a redirection status code should be handled explicitly in your JavaScript? These are good questions, and for simple applications, the answer is very likely that these advanced techniques won't be of any value. However, the Web is no longer a place where simple applications are tolerated; users have become more advanced, customers expect robustness and advanced error reporting, and managers are fired because an application goes down 1% of 1% of the time.

It's your job, then, to go beyond a simple application, and that requires a thorough understanding of XMLHttpRequest. If you can account for the various ready states -- and understand how they differ from browser to browser -- you're going to be able to debug an application quickly. You could even come up with creative functionality based on a ready status and report on a request's status to users and customers. If you have a handle on status codes, your application will be able to deal with a script's errors, unexpected responses, and edge cases. As a result, your application will work all of the time, rather than just in the situation where everything goes exactly right. Add to this the ability to make HEAD requests, check for the existence of a URL, and find out when a file was modified, and you're going to be able to ensure that users get valid pages, are up to date on their information, and -- most importantly -- surprise them with just how robust and versatile your application is.

This article isn't going to make your applications flashy, help you highlight text with fading yellow spotlights, or feel more like a desktop. While these are all strengths of Ajax -- and topics we'll cover in subsequent articles -- they are to some degree just icing on the cake. If you can use Ajax to build a solid foundation, in which your application handles errors and problems smoothly, users will keep coming back to your site and application. Add to this the visual trickery I'll talk about in upcoming articles, and you'll have thrilled, excited, and happy customers. So get your applications running smoothly -- even when things go wrong -- and come back next month to learn about asynchronicity, and how Ajax can truly pat its head and rub its belly all at once (Seriously; Don't miss this next article!).



Back to top


Resources

Learn

Get products and technologies
  • Head Rush Ajax (Elisabeth Freeman, Eric Freeman, Brett McLaughlin, O'Reilly Media, Inc.): Takes the ideas in this article and loads them into your brain, Head First style.

  • Java and XML, Second Edition (Brett McLaughlin, O'Reilly Media, Inc.): Includes Brett's discussion of XHTML and XML transformations.

  • JavaScript: The Definitive Guide (David Flanagan, O'Reilly Media, Inc.): Includes extensive instruction on working with JavaScript, dynamic web pages, and the upcoming edition adds two chapters on Ajax.

  • Head First HTML with CSS & XHTML (Elizabeth and Eric Freeman, O'Reilly Media, Inc.): A complete source for learning XHTML, CSS, and how to pair the two.



Copyright © 2011 - Multiframes - All rights reserved. Conforms to W3C Standard XHTML & CSS