Challenging Preconceptions With Extreme JavaScript And AJAX

Massive advances require massive challenges. The developers of the XB-70 produced a plane capable of flying three times the speed of sound and taught the engineers (and eventually the world) many vital lessons in structural design and avionics. Why am I using this as an example for I.T.? Well – just look at the thing! It looks like nothing that ever came before it, and quite frankly still looks like science fiction compared to the lack-lustre, play-it-safe aircraft of the modern era.

What we in IT can learn from this is: Whilst playing it safe gives us a nice warm feeling, only by pushing our limits will we achieve world beating results! In this article, I will show you how going way beyond conventional wisdom with JavaScript and AJAX can yield spectacular results and reliability that transcend all expectations. Yes – you read that correctly – challenging ideas do not necessarily result in unreliable products. If an approach is inherently superior to its predecessors, it can both be new and more reliable! The techniques described in this article can be seen at Cubical Land Games and in DeployView. If you want to see source code, you'll need to go to Cubical Land. This shows an advanced real time game using standards compliant Javascript, CSS and HTML:

X15 Bombing Game Screen Shot

Step 1: Defensive JavaScript Programming

Most programmers are brought up with (or learn from books) the idea of 'it should work'. That is to say, a programming environment is stable and predicable: A modern computer is a deterministic digital device, each state is a direct consequence of the previous state and so everything should always work the same and you can rely upon each piece working properly all the time. This concept is totally wrong in an Internet environment! Rather than the usual approach of dismissing some piece of code or even an entire browser as 'no good' because it sometimes does wrong things, we have to learn to make program systems that live with failure. No one functional unit should ever be totally dependant on another functional unit working faultlessly 100% of the time. Once you have managed to get your head around coding for failure (defensive programming), you have won the hard part of the battle!

If the very idea of coding for failure is just offending the heck out of you, let us look at some very strong examples of it 'out in the wild'. Probably the best example I can think of comes from modern operating systems. Have you ever wondered by older PC type operating systems used to crash constantly but newer ones stay up reasonably well. Consider Most-Applications-Crash-If-Not-Operating-System-Hangs (sorry Macintosh OS) which crashed with a remarkable regularity whilst OS X is very stable. Also, Windows ME had a truly amazing ability to crash where as Windows XP will stay up long enough to save your work before it dies (normally).

So, why the difference? Well, OS X and XP are 'separate kernel' operating systems. The programs that you run are separated from the operating system core (referred as the kernel). The kernel is designed to tolerate user applications crashing; it just cleans up after them. In old style operating systems the core relied on user applications cleaning up after themselves. If a user application did something wrong it could easily bring the whole operating system down. In other words, modern operating systems are coded for failure!

Step 2: Event Loops And Object Pools

Modern web browsers (I have used these techniques in IE6, IE7, Firefox 1.5 and Konqueror – which means they should also work in Safari) are complex multi-threaded environments. Unlike programming in C, .net or Java, you do not have proper control over the threads in a script running inside a browser. The solution to this conundrum is to program in a thread defensive fashion. You make a program that should stay stable in the face of unexpected threading interactions. The best model I have found for this style of programming is to use event loops and object pools. Do not get me wrong, I am not going to bang on about object oriented programming here, it is just that in this case a sparing use of a few objects can be very handy!

The basic concept of an event loop is that you one 'root' section of code that is run over and over again. This section of code then dispatches 'events' out to other piece of code as and when appropriate. This is a common model in computer programs – especially those employing graphical interfaces. The pieces of code to which events are despatched are called listeners. For Javascript I employ a slight adaptation to the idea. Rather than the looping code having some form of logic that discriminates as to which listener should get which event, I make the event loop very simple indeed. It just 'wakes up' all the listeners periodically and they work out if they should do something.

/* This code handles events  by dispatching activity to 'listener' objects. All that is
 * required of the objects is  that they implement a .fire() method. This is the
 * method which gets called  each time the event loop 'wakes up'.
 */

/* We use two pools. One  holds events to run and the other events to remove.
 * an event listener will  choose when to be removed by calling RemoveEvent(this),
 * however doing so whilst  the event loop is looping through the event listeners
 * could corrupt the event  listener pool. Also, mixing activities of
 * deleting events with  firing events is not especially stable when multiple threads
 * are flying around. So, we  store up all those events which are going to be
 * removed, and then remove  them all after the events have all been fired.
 */
var eventPool=new  Array();
var removePool=new  Array();
/* Each even object is given  a unique id, this helps with house keeping. */
var eventId=0;
/* As we live in a complex,  multi-threaded environments, it is much more stable
* to put core data access  algorithms inside a function which other functions
* can then call. Also note  that there must never be ten million events at once
* so it is safe to wrap the  ID at this point to avoid the (probably mythical)
* chance of numeric  overflow.
*/
function  GetNewEvId()
{
  return eventId >  10000000 ? 0 : eventId++;
}
/* Again, it is much more  stable to put data manipulation and algorithms that
 * manage core activities in  functions. This one adds an event listener to
 * the pool of event  listeners
 */
function  AddEvent(o)
{
  o.eventId=GetNewEvId();
  eventPool.push(o);
}

/* This function queues an  event listener for removal from the event pool.
*/
function  RemoveEvent(o)
{
  removePool.push(o);
}

/* This function (not the  best name in the world) removes a particular event from
 *  the event pool. It would  be possible to create an algorithm that removes all of
 * of them at once and scales  better than this approach, but as removal is very much
 * rarer in this application  than firing, it was not worth the extra complexity.
 *
 * The fact that all event  listeners are given a unique ID comes into play here, because
 * the algorithm can be  passed the object to remove and then recognise it in themselves
 * event pool from its  ID.
 *
function  RemoveEvents(o)
{
  var  id=o.eventId;
  var n=new  Array();
  var l=  eventPool.length;
  for(var  i=0;i
  {
    var  oo=eventPool[i];
    if(oo.eventId!=id)
    {
      n.push(oo);
    }
  }
  eventPool=n;
}

/* This is the core method of  the event queue. It is called from the 'onload'
 * event of the   element of the document. From then on, it sustains
 * the event loop by its  self. It performs events based on a 'frame rate'
 * The frame rate is actually  a maximum frame rate as it is set by a delay
 * between firing all the  events and firing them again. The more time firing
 * the events takes, the slow  the actual frame rate.
 *
 * It might seem more logical  to set a timeout for each event individually, but
 * this does not work as you  might expect. What happens is that the browser will
 * choose when to actually  run the methods passed to 'settimeout'. It may run
 * at any point after the  timeout set. Under heavy load this tends to result
 * in nothing happening for a  while and then a bunch being run in short succession.
 * In a game environment this  is a disaster because the game become 'jerky'.
 *
 * By reducing our reliance  on the timing of the browser (defensive programming)
 * we are making the code  behave more like we want it to under adverse conditions.
 *
 * Once all the events  are fired, any events that are queued for removal are
 * removed.
 */
function DoEvent()
{
  var l=  eventPool.length;
  for(var  i=0;i
  {
    eventPool[i].fire();
  }
  l=removePool.length;
  for(var  i=0;i
  {
    RemoveEvents(removePool[i]);
  }
  removePool=new  Array();
  setTimeout('DoEvent()',timeoutValue);
}

This picture is here because there is too much text in one lump otherwise :-)

How Does All This Apply To AJAX?

Now that we have a few tricks to make stable timing loops and queues in JavaScript, we can use these ideas to improve the way we use AJAX: AJAX actually admits it might go wrong! A web server is perfectly within it rights to not serve a page for any reason. The main reason is overload, though many others exist. This means that we should 100% absolutely never rely of a AJAX call returning with anything meaningful! If we require some degree of reliability using AJAX, defensive programming for failure is a must.

Here is a simple example of updating a database using AJAX and reflecting the results of the update back to the user. The Code has been cut down in places so that the core parts are easier to see.

/* The concepts in the  previous example are applied here as well:
 * Communication with the  server via AJAX is considered to be an
 * event.  The event is fired  periodically until a successful return
 * comes from the  server.
 *
 * This system accumulates  instructions to invert the status of a day
 * and submits the  instructions once every x milliseconds (currently 500).
 * If there are no inversions  to submit - it does nothing.
 * This means that lots of  individual 'clicks' are grouped together
 * and so prevents server and javascript overload.
 */
  var invertQueue=new  Array();
  /* As before, we place  updates to core data structures in functions */
  function  pickerAddElemToInvertQueue(elem)
  {
    invertQueue.push(elem);
  }
  /* This function tries to  update each item that is stored in the
   * the invert queue.  We do  not hold listener objects in this
   * example as we do not  require that much flexibility.  All we
   * ever do it update or check  the status of particular Ids.  So
   * queues just hold the IDs.
   *
   * Also note that if an even  actually works it is always removed
   * from the queue. This leads  to the architecture here where
   * we empty the queue and  then refill it with any failed events.
   * However, the process has  two steps just like in the previous
   * example – so is still  stable.
   */
  function  pickerCheckInversionQueue()
  {
    if(invertQueue.length>0)
    {
      // use a loop to  drain the queue to ensure there are
      // no multi-threading  issues
      var toInvertList=new  Array();
      while(invertQueue.length>0)
      {
        toInvertList.push(invertQueue.shift());
      }
      // Send the  toInvertList to the server and get (hopefully) a
      // reply for each  one
      var rpcObj=new  XmlRpcClient('--A URL --');
      rpcObj.debug=false;
      try
      {
        // Get the list  of utc second 'days' to
        // invert from  the id's of the elements
        var utcList=new  Array();
        for(var  i=0;i<toinvertlist.length;++i)
        {
            utcList.push((new String(toInvertList[i].id)).substr(4));
        }
        var  reply=rpcObj.execute
        (
          'InvertWorkingStatus',
          pickerGetLocationId(),
          utcList
        );
        var  working=reply.Working;
        var  nonWorking=reply.NonWorking;
        for(var  i=0;i<working.length;++i)
        {
           /* ... Code  To Update Document's DOM ... */
        }
        for(var  i=0;i<nonworking.length;++i)
        {
           /* ... Code  To Update Document's DOM ... */
        }
      }
      catch(e)
      {
           /* ... Code to  send debug to user if wanted ... */
      }
      // Failed to  update, so put them back on the queue
      while(toInvertList.length>0)
      {
        pickerAddElemToInvertQueue(toInvertList.shift());
      }
    }
  }
}
/* This function creates the  event loop.  It fires all the update
 * events, then calls its  self after a 500 milliseconds.
 * The first call to this  function must come from the 'onload'
 * event of the document  body.
 */
  function  pickerCheckQueues()
  {
    pickerCheckInversionQueue();
    window.setTimeout('pickerCheckQueues()',500);
  }

Contrast To Raw AJAX

This structured, controlled and resilient approach to AJAX could not be more different to the standard 'fire and forget' mess that has started to give this powerful Web techniques a dodgy reputation...

Can you spot the difference:

Which one is real and which is a cruddy kit car?

One of the above is the real thing. The other has a superficial resemblance to the real thing, but will disappoint! This is the difference between a system designed to function in the unstable Web/AJAX world and one that has been 'just built'.

Let us review the differences between the approach given here and 'just build it' AJAX:

First off – the AJAX example given does not rely on the HTTPRequest its self to provide asynchronisity. Indeed, the HTTPRequest as sent synchronously. The code uses the 'settimeout()' built in JavaScript function to run the AJAX in a separate thread of activity. If one was to launch (as is often done) many AJAX calls at once, and rely on the browser to provide asynchronous call back, one has no control over the order in which the calls will return. There is also a good chance that you will have multiple threads colliding in the functions which update the core data structures. This will lead to corruption of those structures and system failure.

Secondly – this approach has an ordered failure management system and an effective mechanism for retry of failed calls. Compare this to catching failures in a callback function. In that more primitive approach, each time there is a failure one would raise a new request straight away – which could potentially raise a 'request storm' on the server. If you were to place the re-request in a settimeout() then you could have vast numbers of settimeout() calls running at once, which will produce very poor performance in the browser or even resource exhaustion.

Thirdly – this system is extensible. Consider that we found the 500 millisecond gap too short where the server was overloaded. We could add in an algorithm by which the gap increased should more than a cut off number of failures occur. This would mean the system as a whole (clients and server) could co-operate in achieving maximum possible performance. Such client/server interactions are 100% necessary if we are to achieve the sort of flexible, responsive Web experience we all want.

Now that we have a stable architecture for AJAX, we can push the limits of what we do with it; we can make truly interactive web pages where multiple people can communicate with a central system in real time!

A Final Thought

Below is an enhanced version of a long distance photograph showing the end of AV/2, one the the only two XB70's ever built (sometimes called the YB70). The pilot of the chase plane which struck AV/2 and one of the two pilots of the experimental aircraft its self lost their lives because of this mid-air collision. Whilst this was a tragedy, they were test pilots and so taking risks was part of their job. People like these risk their lives to help progress technology.

In the modern era we seem to have become obsessed with the alleviation of risk. In the IT industry, we only take steps which are highly predictable. That will only ever produce predictably under-performing results. I hope that in this article I have shown that by taking a more radical approach to existing techniques, we can push forward and stop wasting time with 'OK' solutions. We should grasp the nettle and make amazing solutions!

Dr Alexander J. Turner