Supplementary

A Small JavaScript-Library that Automates working with XMLHTTPRequest

Using createXHR

XHRFactory.createXHR creates an XMLHTTPRequest object that is appropriate for the platform on which the application is running and, in this respect, incorporates the browser detection tests that are necessary to achieve this.

Importantly, this function configures the XHRFactory object such that the browser tests are performed only once when it is first called. This means that subsequent calls to createXHR are extremely efficient and considerably faster than the alternative, which would be to perform browser detection redundantly every time an XHR object was required. createXHR is also used internally by XHRWrapper objects, thus lending them maximal efficiency.

However, callers of createXHR must also furnish a full response-handler for each XHR object created, and are responsible for all other aspects of using XHR objects such as calling their open, and send methods etc.

Given this, and should you use this method in preference to XHRWrapper objects, you will gain a very minor performance-advantage in terms of XHR object-creation, but this will come at the expense of having to negotiate the minefield of bugs and inconsistencies that XHR exhibits on the various platforms.

The listing sketches out the use of the createXHR method.


 try
    {
    var XHRObj = XHRFactory.createXHR ();

    XHRObj.open (...);

    XHRObj.onreadystatechange = function ()
       {
       if (XHRObject.readyState === 4)
          {
          if (ObjRef.status === 200)
             {
             // Process data returned from server

             alert (XHRObj.responseText);

             }

          else
             {
             // Report error to user

             ...

             }

          }

       }

    XHRObj.send (...);

    }

 catch (E) { ... }
            

Creating XHRWrappers

Using XHRFactory.createXHRWrapper is the considerably easier route to working with XHR objects. This method creates an object that contains a reference to an XMLHTTPRequest object, and which exposes a very simple interface to client code. Example 1 illustrates creating an XHRWrapper object.

The principal method that the XHRWrapper type supports is called doTxn. This manages communication with the server, and accepts a number of arguments that reflect the arguments accepted by the open and send methods of the underlying XHR object. Using doTxn is explored in the following sections.

Note that an XHRWrapper object can be reused as many times as desired, but that a new transaction should not be started using a wrapper that is still conducting a previous transaction.


 // Example 1

 try
    {
    var XHRWrapper = XHRFactory.createXHRWrapper ();

    ...

    }

 catch (E) { ... }
            

Retrieving Data

To retrieve data from the server the application must supply doTxn with a URL, an HTTP method-string, and a reference to a response-handler function. This is shown in Example 2. Note that such a call is asynchronous by default.

doTxn calls the open and send methods of the XHR object that the XHRWrapper contains, and manages the finer details that surround this process. Critically, however, doTxn also implements a 'partial' or 'base' response handler that comes into play whenever the server responds.

Normally, when working with XMLHTTPRequest, processing any data returned from the server requires each response handler to contain logic that tests the readystate and status attributes of the object concerned, as the initial listing on this page illustrates. However, the repetition of such code in every response handler within an application constitutes unwanted clutter that impinges on readability and, from there, comprehension on the part of the developer. The extra code also harms efficiency.

doTxn resolves these problems by implementing an inner function that performs the testing, and which calls the response handler provided by the application only if the transaction has completed successfully. This allows all such response handlers to remain free of redundant logic, thus making them considerably leaner and simpler, and thus making the application more efficient.

Example 2, demonstrates these points.


 // Example 2

 //
 // Contents of hello.php
 //
 // <?php  echo "Hello World";  ?>
 //

 function onXHRSuccess (XHRObj)
    {                            // No testing for a successful response is required here. All of
                                 // that is performed in doTxn, meaning that this function will be
    alert (XHRObj.responseText); // called only if the transaction has completed, and has
                                 // completed successfully. All this function need do, therefore,
    }                            // is process the data returned.


 try
    {
    var XHRWrapper = XHRFactory.createXHRWrapper ();

    ...

    XHRWrapper.doTxn ("GET", "hello.php", onXHRSuccess);

    }

 catch (E) { alert (E.message); }

 --------------------------------------

 Output:

 Hello World
            

Synchronous Transactions

XHR transactions can be synchronous or asynchronous. By default, doTxn connects with the server asynchronously, but client code can stipulate a synchronous transaction by passing a value of false as the fifth parameter.

A synchronous retrieval is illustrated in Example 3. Note that the role of the fourth argument is explored below, and can be set safely to null in this example.


 // Example 3

 //
 // Contents of hello.php same as in Example 2
 //

 try
    {
    var XHRWrapper = XHRFactory.createXHRWrapper ();

    XHRWrapper.doTxn ("GET", "hello.php", onXHRSuccess, null, false);

    alert            ("Performing Second Transaction");    // This will execute only after
                                                           // OnXHRSuccess has executed

    XHRWrapper.doTxn ("GET", "hello.php", onXHRSuccess, null, false);

    }

 catch (E) { alert (E.message); }

 function onXHRSuccess (XHRObj)
   {
   alert (XHRObj.responseText);
   }

 --------------------------------------

 Output:

 Hello World
 Performing Second Transaction
 Hello World
            

Aborting Transactions

To abort a transaction, simply call the abort method of the XHRWrapper object in question. (Note that, most browsers will not abort a synchronous transaction.)

In Example 4 the code calls doTxn (invoking a server-side service asynchronously that we assume will take a long time to respond), and passes onXHRSuccess as the response handler. However, it also sets a timer subsequently to invoke onTimeout if onXHRSuccess does not execute within three seconds.

If that function does execute, it cancels the timer, but if onTimeout executes first it calls the abort method of the XHRWRapper, which terminates the transaction with the server.

Note that calling the abort method of the underlying XHR object will also cancel a transaction, but that on browsers such as Firefox this will cause execution of the response handler. Using an XHRWrapper's abort method guards against this.

Given this, if you use doTxn, you should use XHRWrapper.abort to halt the transaction, rather than the abort method of the underlying XMLHTTPRequest object.


 // Example 4

 //
 // Contents of take_forever.php
 //
 // <?php
 //
 // for ($A = 0; $A < 10000000; $A++) { }
 //
 // ?>
 //

 function onXHRSuccess (XHRObj)
    {
    clearTimeout (Timer);
    alert        (XHRObj.responseText);
    }

 function onTimeout ()
    {
    XHRWrapper.abort ();
    alert            ("XHR transaction timed-out");
    }

 try
    {
    var XHRWrapper = XHRFactory.createXHRWrapper ();

    XHRWrapper.doTxn ("GET", "take_forever.php", onXHRSuccess);

    var Timer = setTimeout (onTimeout, 3000);

    }

 catch (E) { alert (E.message); }

 --------------------------------------

 Output:

 XHR transaction timed out
            

Sending Data

Should the application wish to submit data to the server, then it should use doTxn, as before, also supplying a URL and an appropriate HTTP method-string as before, such as 'POST' (in principle, 'PUT should also work, but this depends on the server in question). It should also provide a string, containing the data to send, as the fourth argument to doTxn.

If the application must process a response from the server then a reference to a response handler should be passed as the third argument, as in the previous examples.

Example 5 illustrates these ideas.


 // Example 5

 //
 // Contents of post_service.php
 //
 // <?php  echo $_POST['Value'];  ?>
 //

 function onXHRSuccess (XHRObj)
    {
    alert (XHRObj.responseText);
    }

 try
    {
    var XHRWrapper = XHRFactory.createXHRWrapper ();

    XHRWrapper.doTxn ("POST", "post_service.php", onXHRSuccess, "Value=42");

    }

 catch (E) { alert (E.message); }

 --------------------------------------

 Output:

 42
            

Using Multiple XHRWrappers

Multiple XHR objects and XHRWrappers can be created and manipulated independently of each other. Example 6 demonstrates this, where two XHRWrappers are created and communicate with the server.

Note that if you perform multiple and concurrent asynchronous transactions then you may run the risk of race conditions, and the solution, should these arise, is to use the Event Marshaller library that is also available from this site.


 // Example 6

 //
 // Contents of hello.php same as in Example 2
 //
 // Contents of goodbye.php
 //
 // <?php  echo "Goodbye World";  ?>
 //

 function onXHRSuccess (XHRObj) { alert (XHRObj.responseText); }

 try
    {
    XHRFactory.createXHRWrapper ().doTxn ("GET", "hello.php",   onXHRSuccess);

    XHRFactory.createXHRWrapper ().doTxn ("GET", "goodbye.php", onXHRSuccess);

    }

 catch (E) { alert (E.message); }

 --------------------------------------

 Output (concurrently):

 Hello World
 Goodbye World
            

Handling Errors

Inevitably there are times when a transaction will fail, and client-side code should be equipped for such eventualities. Given this, doTxn accepts an optional reference to a user-defined error-handling function as its seventh argument.

If client code provides such a reference, and the transaction with the server fails (i.e. if the status attribute of the underlying XHR object does not hold a value of 200) then doTxn will invoke the function indicated, passing that object.

The error handler should test the status member of this object to determine the nature of the problem, and Example 7 illustrates the basic idea.


 // Example 7

 //
 // Assume that non_existent.php does not exist
 //

 function onXHRSuccess (XHRObj) { }
 function onXHRError   (XHRObj)
    {
    alert (XHRObj.status);
    }

 try
    {
    XHRFactory.createXHRWrapper ().doTxn ("GET", "non_existent.php", onXHRSuccess, null, true, null, onXHRError);

    }

 catch (E) { alert (E.message); }

 --------------------------------------

 Output:

 404
            

xHRFactory_DbC

xHRFactory_DbC ('XMLHTTPRequest Factory Design by Contract'), is an optional adjunct to XHR_Factory and its progeny that uses the AspectJS AJS object to apply a suffix to createXHRWrapper. That suffix applies a prefix to the doTxn method of each XHRWrapper object generated, and that prefix tests the parameters passed to doTxn, throwing an exception whenever bad arguments are detected.

Only one call to xHRFactory_DbC is required, and all code that calls the methods implemented by the library remains unchanged and continues to operate normally. Argument-checking can therefore be enabled and disabled trivially, thus giving equally trivial control over the overheads it incurs, and thereby affording programmers all the benefits of a design-by-contract approach during development, without its costs when the system is deployed.

Using xHRFactory_DbC

To use xHRFactory_DbC you must import the code for the function along with the AJS object-definition. To enable parameter checking for calls to XHRFactory and the methods of the XHRWrappers that it generates, simply make a single call to xHRFactory_DbC as soon as possible in the application's run, as Example 8 illustrates.

Examining the parameters that xHRFactory_DbC accepts:

  1. The first parameter must be a reference to the XHR_Factory object.
  2. The second parameter must be a reference to version 1.1 of the AJS object. It is necessary to pass this explicitly because you may have chosen to make the AJS object a member of another object, thus taking it out of global scope. Additionally, you may have chosen (for reasons known only to you) to change the name of the AJS object, in which case you should supply that new name as the third parameter.
  3. The third parameter (the CallPoint argument) is optional and fulfills the same role as the CallPoint does in AspectJS method signatures. See the relevant section in the AspectJS tutorial for more information.

Failure to supply the first two parameters, or to provide arguments of an incorrect type, will cause xHRFactory_DbC to throw an Error object, whose message member will contain a detailed description of the problem (including the contents of CallPoint, if the caller provides a value for that argument). Additionally, and given that it is necessary to call xHRFactory_DbC only once, calling it again will also raise an exception.

Note that, once enabled, there is no way to turn error checking off during a given run of the application.


 <!-- Example 8 -->

 <html>
    <head>
       <script type = "text/javascript" src = "AJS.js"           ></script>
       <script type = "text/javascript" src = "XHRFactory.js"    ></script>
       <script type = "text/javascript" src = "xHRFactory_DbC.js"></script>

       <script type = "text/javascript">

       xHRFactory_DbC (XHRFactory, AJS, "Example 8 - Point A");

       ...


       </script>
    </head>

    <body> ... </body>

 </html>
            

Effects of xHRFactory_DbC

Once called, subsequent calls to XHR_Factory and the methods of the XHRWrapper objects it generates will continue to operate normally, but with the benefit of rigorous checks on any arguments that client code supplies it. To disable this error checking, and thereby lose the overhead it entails, simply comment out the call to xHRFactory_DbC, and disable any statement that loads xHRFactory_DbC's code.

Calling xHRFactory_DbC also augments doTxn's signature with an argument called CallPoint that serves the same purpose as the CallPoint argument carried by xHRFactory_DbC itself. Note that it is entirely safe to leave a value for the CallPoint argument in place whenever xHRFactory_DbC is disabled, as it will be ignored.

The effects of using xHRFactory_DbC are illustrated in Example 9, which calls the function to put argument checking in place, and which then makes a selection of incorrect calls to doTxn. Those calls are trapped by the prefix that is attached to that function, and the exceptions generated are caught and reported.


 // Example 9

 function onSuccess () { }

 xHRFactory_DbC (XHRFactory, AJS, "Example 8 - Point A");

 var XHRWrapper = XHRFactory.createXHRWrapper ();

 var NotAString = 1;

 try { XHRWrapper.doTxn ("GET",  NotAString,  onSuccess,     "",         true, null, null, "Point B"); }  catch (E) { alert (E.message); }
 try { XHRWrapper.doTxn ("GET", "URL",       "NotAFunction", "",         true, null, null, "Point C"); }  catch (E) { alert (E.message); }
 try { XHRWrapper.doTxn ("GET", "URL",        onSuccess,     NotAString, true, null, null, "Point D"); }  catch (E) { alert (E.message); }

 --------------------------------------

 Output:

 Error in call to doTxn method of XHRWrapper object - URL argument is not a string. Client-code call point: Point B
 Error in call to doTxn method of XHRWrapper object - respHandler argument does not refer to a function. Client-code call point: Point C
 Error in call to doTxn method of XHRWrapper object - Data argument is not a string. Client-code call point: Point D