User-Proofing Ajax
Issue № 228

User-Proofing Ajax

We’ve all felt the clean slickness of a well-oiled Ajax app. But think back to the last time something went wrong while you were using an Ajax app: how did it feel?

Article Continues Below

Chances are, it felt awkward and foreign. You probably stared at the little spinning “wait” graphic and wondered whether or not you should reload the page. You might have glanced at your watch and thought that the app was taking its sweet time—or maybe you wondered if your request had been made at all. You didn’t know what was going on—and you’re a developer! Imagine what a non-technical user goes through every time there’s a hiccup.

When good apps go bad#section1

Ajax is handy because it avoids needless browser behavior such as page reloads, but it can also be dangerous because it avoids useful browser behavior like error handling. Gone are the “Server not found” error messages and that familiar escape hatch, the Back button. When Ajax circumvents useful behavior, we have to make sure we replace it as well as we can.

Perfecting your backup plan#section2

One of the cardinal rules of client-side scripting is to accommodate users who have scripting turned off. Ajax implementation makes this easy: when hijacking (or “hijaxing”) a link or form to trigger your Ajax functionality, make sure that link or form resolves to a valid URI that will carry out the intended action using the traditional method. (Line wraps marked » —Ed.)

<form action="traditional_action.php" method="post" »
onsubmit="perform_ajax_action(); return false;">

If you take this approach, script-enabled browsers will carry out your Ajax actions and other browsers, recognizing neither the onsubmit event handler nor its return false directive which skips form submission, will submit the form normally and fall back to the traditional form action.

(Note: it’s almost always a better idea to do the hijacking deep in your JavaScript code. Inline JavaScript event handling is best suited to, well, demonstration.)

We could also take this a step further in anticipation of script-enabled browsers that don’t support any type of XMLHttpRequest object. Instead of using an explicit return false in the onsubmit event handler, we’d test for Ajax ability in the handling function, carrying out the Ajax script if it’s supported and triggering the form if not. Let’s say you have a JavaScript function named createAjaxRequest() that creates a browser-appropriate XMLHttpRequest object or returns false on failure:

var request; // our request object

function perform_ajax_action(){
  if ( !request = createAjaxRequest() ){
    return true; // abort Ajax attempt, submit form
  }
  // proceed with your regular Ajax functionality
  // ...
  return false; // do not submit the form
}

At the form level, we’d rewrite our event handler to fall back to regular form submission at the first sign of trouble. (Line wraps marked » —Ed.)

<form action="traditional_action.php" method="post" »
onsubmit="return perform_ajax_action();">

If all goes well, the Ajax code will be executed and the form submission will be skipped. If any problems occur with the user agent’s Ajax support, the form will be submitted and the traditional version of the action will be performed.

Keeping your users informed#section3

Much Ajax functionality exists to bypass the standard browser interface, so we need to replace the core usability safeguards that your users are accustomed to. First, we need an error handler that reports errors to your user when the browser can’t. The way in which you deliver the error reports depends on your individual application; in my examples, I put the error message in a hidden div that appears when an error occurs.

What kind of errors do we report? Connectivity errors, to begin with. If there’s a problem connecting to your behind-the-scenes files, the browser won’t report it natively. If the response fails with a regular HTTP error code, we can pull that from the XMLHttpRequest object and report it properly. Here’s an excerpt from the function set as the onreadystatechange event handler, with the XMLHttpRequest object request: (Line wraps marked » —Ed.)

if ( request.readyState == 4 ){  // 4 is "complete" 
  if ( request.status == 200 ){
    // HTTP OK, carry out your normal Ajax processing
    // ... 
  }else{
    // something went wrong, report the error
    error( "HTTP "+request.status+".  An error was »
             encountered: "+ request.statusText );
  }
}

The next type of error to address is the application-level error. These errors occur when something goes wrong within your application, and though the way in which you handle these errors depends on your specific application, there are some basic principles to keep in mind.

Let the cycle be unbroken#section4

Most importantly, the Ajax communication cycle must be preserved. Your server-side execution must complete and return a valid XML document—otherwise, you leave the browser (and user) hanging, with no clue as to what’s happened. If your server-side script crashes, the error message will be lost and your application will silently fail. That’s why it’s essential to perform assertions and boundary checking on anything in your code that could crash your program, like this PHP snippet which logs an error and exits when a file can’t be opened:

if ( !$fp = fopen( $filename, "r" ) ){
  $error_msg = "Could not open file " . $filename;
  exit();
}else{
  // proceed with the rest of the program
  // ...
}

This is good programming practice in general, but in this situation, it’s vital to your application’s usability.

Communicate errors#section5

Once your server-side program completes, you should check for success and report any errors to the user. The XML document sent back from the server should show error reporting—even something basic with just a code and a message.

In this example, the XML document has a success element, which contains the value “1” for success and “-1” for failure. The XML document also carries an error element, which contains the error message to be reported. The server-side script performs error checking, and returns valuable error messages to keep the user informed, and guide him or her through the user process. (The errors that the server-side script checks for in the example are simple content validation tests that should of course be performed with client-side validation; it’s a very basic Ajax app put together only for demonstration purposes.)

Solving the real Ajax killer: silent errors#section6

What about silent errors like timeouts and dropped packets? When confronted with a web application that appears to have mysteriously ceased to function, users generally respond by losing patience, hitting the Back button, and trying again. This probably won’t be the ideal case for your Ajax app: with its lack of complete page reloads, the Back button will take the user back multiple steps in the user process. If the Ajax app is well-designed, no information should be lost—after all, you’ve been saving data to the server the whole time—but it’s still an extra step for the user that can be avoided.

How? Keep your user informed. Keep track of the time elapsed since you made your request. If a longer-than-reasonable amount of time goes by before you get a response, pop up a warning dialogue informing the user that something may have gone wrong. Explain the situation and offer them options: at the very least, give them a choice between waiting a little bit longer and reloading the page. It’s a simple but effective catch-all error handler that maintains user confidence in your application.

This second example puts this idea to use, letting you set a server-side delay to simulate a packet delay. If a request is made and more than 30 seconds go by with no response, a dialogue pops up asking the user if they’d like to do something about it. Try varying the server-side delay time to see how it feels. Pretend you’re on a dial-up connection and are very used to dropped packets. You’ll be surprised at how appreciative you are of the application that’s sensitive to your frustration.

Only the beginning#section7

This is by no means a comprehensive guide to error handling with Ajax. There are many other ways to use Ajax to enhance your application’s user experience, and many more problems and solutions will undoubtedly surface as Ajax grows and matures. Whatever methods you use, remember that Ajax is meant to create a graceful, cohesive user experience, so make sure your error handling fits with that principle and complements your application. The more graceful and cohesive your error handling, the more confidence your app will inspire—even when it’s breaking down.

About the Author

Peter Quinsey

Peter Quinsey is a freelance web designer/developer/analyst/architect/end user. Based in Toronto, Ontario, he specializes in whatever he happens to be working on at the moment.

22 Reader Comments

  1. Beside the client side error handling this (well written) article described, it is first of all essential, that the server side indicates errors in a proper way. Many scripting languages like PHP were made for producing HTML. Now that they have to produce an XML document for the Ajax-client you have to customize the error handling to be integrated into the XML document and/or generate the right HTTP response code.

  2. Having a coding background, there are some nits with the code that wouldn’t have passed a code review. Considering the ‘leader-of-the-pack’ status of ALA, people _will_ copy-paste examples given here, and why have them copy-paste less-then-perfect examples? 🙂

    * The test in the ‘keep users informed’ example should be rewritten in reverse (if (request.status!=200){ error(…);}). Saves an indent and unobfuscates code.

    * In the PHP-code example; after an exit() in an if()-statement there is no need for an else. Saves an indent.

    The “second example” that pops up a box ‘please wait’ is a very nice touch indeed!

  3. Eugen, Maarten, thanks for the feedback.

    I tried to minimize specific details of a server-side error-handling implementation, aiming instead to stick to general principles which will apply across all platforms, languages, and/or frameworks.

    In that light, I tried to keep the code examples as skeletal as possible. I would much rather have someone read the article and say “ah, I know how I can improve my app’s error handling”—but of course this _is_ the internet, and people _will_ copy and paste. Thanks for the code tweaks.

  4. logically, if perform_ajax_action is successful, it should return true…so

    onsubmit=”return perform_ajax_action();”

    should be

    onsubmit=”return !perform_ajax_action();”

  5. sorry, should have expanded the first part of my comment: if the function perform_ajax_action fails to create an ajax request, it should return false, not true…

    more generally: if i call a function and the function fails to perform, it should return false, not true (from a good practice/clarity point of view). hence, imho, it should be:

    function perform_ajax_action(){
    if ( !request = createAjaxRequest() ){
    return false; // abort Ajax attempt, submit form
    }
    // proceed with your regular Ajax functionality
    // …
    return true; // do not submit the form
    }

    and then deal with it via

    onsubmit=”?return !perform_ajax_action();”?

  6. I agree to Patrick, that it’s common sense for developers to return false if the execution of a method failed. Same is for JavaScript onSubmit event handlers, because they were meant for checking form values and then when the check failed you should return false and the form should not be submitted.

    With Ajax it’s vice versa, the form should not be submitted if everything is ok.

    A solution to this is the usage of an onClick event handler on the submit button that triggers the Ajax action and an onSubmit method that just checks for errors of the Ajax action. Then the true / false logic is right again.

  7. How do you write a single server-side action that:

    1) returns xml for valid ajax request
    2) returns full html/xhtml,or forwards to full url, if standard form post?

  8. Do you typically write a single server-side handler that works for ajax and standard form posts? If so, how do you detect which occurs, so you return full HTML on a form post, or a redirect, rather than the Ajax xml snippet?

  9. Patrick and Frank nail the return value problem on the head: a function that fails should return false, but an onsubmit’s event handler should return true if the Ajax functionality fails. So at some point you’re going to have a logical disconnect in your code, whether it’s returning false upon success or negating the return value at the event level.

    One way of getting around this: instead of triggering the form submission using a return value, trigger the form submission directly from within your function:

    function perform_ajax_action() {
      if (!we_support_ajax()) {
        document.forms[0].submit();
        return false;
      }
      // rest of Ajax functionality goes here
      return false;
    }

    Yes, you’re still returning false upon success, which is logically incorrect, but the logical importance of the return value is diminished since we no longer depend on it to trigger form submission.

  10. bq. Do you typically write a single server-side handler that works for ajax and standard form posts? If so, how do you detect which occurs, so you return full HTML on a form post, or a redirect, rather than the Ajax xml snippet?

    For a more complex implementation, this is a good idea. The ideal implementation would have three server-side components:
    # the server-side functionality
    # the Ajax interface to the functionality
    # the traditional interface to the functionality

    How these are implemented depends on the scale of your app: separate files, separate functions, separate single lines of code.

    There are a number of ways to detect whether to use the Ajax interface or the traditional interface. You could add an extra GET variable to the Ajax call to act as a flag. You could use POST for the traditional form submission instead of GET (semanticists may take issue with this, depending on what your form’s task is). Or you could just give each interface a separate file.

  11. “beside the client side error handling this (well written) article described, it is first of all essential, that the server side indicates errors in a proper way. Many scripting languages like PHP were made for producing HTML. Now that they have to produce an XML document for the Ajax-client you have to customize the error handling to be integrated into the XML document and/or generate the right HTTP response co”

    I dont think you understand ajax.

  12. Its good to see someone giving some effort to error handling in ajax.
    I tried searching for quite a while before resorting to my own experiments.
    I thought some people out there might find this useful.

    Any number of problems can arise which may result in communications problems
    between your ajax application and the server and unfortunately the different
    XMLHttpRequest objects do not handle them in a standard way.

    The server may be offline or your route to the server may be down resulting in
    not being able to establish a connection, a 30 second timeout is not the best
    solution here.

    opera 9 will call onreadystatechange with req.readyState=4 and req.status=0
    firefox 1 will call onreadystatechange with req.readyState=4 and req.status exception
    ie 6 will call onreadystatechange with req.readyState=4 and req.status=12029

    Those are easily enough detected, use try and catch to handle the exception.
    Your connection to the server could unexpectedly close due to network problems
    or a server crash. These are much harder to deal with.

    opera 9 will call onreadystatechange with req.readyState=4 and req.status=0
    ie 6 will call onreadystatechange with req.readyState=4 and req.status=12152
    firefox 1 will call onreadystatechange with req.readyState=4 and req.status=200

    The firefox response is a big problem here, 200 is the HTTP status code sent by the
    server to indicate success but firefox will give a status of 200 if the server disconnects
    without sending any status code at all which means it is not reliable to use a 200 status code
    as an indication of success. If we were posting a message on a bbs when the error occured then
    firefow would indicate success but our message would not be posted.

    So how should we detect a successful ajax request? a fully valid response document would be
    one indication but not all ajax requests return a document, XML, JSON, plain text or anything.
    We could use another status code but there are problems there too.
    The code which should be returned if there is no document body is 204 “No Content”,
    unfortunately if we return 204 then opera9 will not call onreadystatechange so our ajax
    application does not see a response at all. IE6 will call onreadystatechange with
    req.readyState=4 and req.status=1223 instead. Fortunately 202 “Accepted” does work reliably.

    There are similar problems with HTTP status codes in the 500 range (Failure codes).

    Believe it or not it is possible to code your ajax application to work reliably despite all
    these problems but it helps if you know what to expect.

  13. One highly useful technique is the use of “unit tests”:http://en.wikipedia.org/wiki/Unit_test for your Javascript classes. Many developers working in other languages/problem domains rely heavily on unit tests to ensure things work the Way They Should. A unit test is a methodology for taking program “units” (modules, classes… basically any encapsulated group of code) and subjecting it to a comprehensive set of tests, which determine whether it behaves as the author(s) of the code intended.

    There are some Javascript unit testing frameworks (“JSUnit”:http://jsunit.net , the Scriptaculous “testing components”:http://wiki.script.aculo.us/scriptaculous/show/Testing ) that can help you accomplish this.

    I have found unit tests can help with improving AJAX apps. A unit test cannot be comprehensive of all the millions of weird ways a user might use your app. But, unit testing helps me remember to encapsulate functionality, even when writing JS, and to provide consistent, well documented interfaces in my classes. The biggest gain, though, is in troubleshooting. If I find an error, and know that the class the functionality relies on is passing its tests, it is typically safe to assume that the issue isn’t a coding issue in the class itself, but lies somewhere else.

    For a concrete example, I was recently having a problem with an AJAX app I’m working on that deals with statistical data manipulation. Because I’d written tests for my data manipulation classes, when I hit an error, I was able to quickly ascertain that the classes were manipulating the data as specified but that my event registration code was firing prior to the creation of the elements it was supposed to be attached to. Nothing fancy there, but given how easy it is to run into silent errors in AJAX scripting, any methodology that reveals problems rapidly and before an error becomes a Real Problem is a useful methodology.

  14. I’m already using the “hijaxing”(nice term) technique successfully but I’m very impressed with the second example. Timeouts in ajax apps ARE a pain in the rear and your solution for handling it is really nice.

  15. The most used nav button in any website is the browser’s back button. Clicking “back” when something goes wrong is a strategy that each of us employs.

    We expect to see pages reload too. These are conventions that we have learned in the first 15 years or so of the web. We are going to have to unlearn them, however, if AJAX and other Rich Internet App techniques are to prevail.

    For this shift in the web design paradigm to occur, designers and developers will have to meet half-way. Until now, I wasn’t sure how this was going to happen.

    But, you’re right, it’s as simple as adding a timer to the web page and passing messages to the user, depending on how long they have been waiting.

    One difficulty with the temporal approach, however, is that users on different connection speeds will have different wait times. That could pose a problem for international sites.

  16. I am using ajax for submitting the form.
    I received Error 12002 in IE .
    I want to know what is the reason.
    Please reply me .

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

Nothing Fails Like Success

Our own @zeldman paints the complicated catch-22 that our free, democratized web has with our money-making capitalist roots. As creators, how do we untangle this web? #LetsFixThis