Section 10.1. Call Tracking


10.1. Call Tracking

Asynchronous, Follow, Monitor, Parallel, Tracking, XMLHttpRequest

Figure 10-1. Call Tracking


10.1.1. Developer Story

Dave is writing an Ajax chat client, and he's concerned the more vocal users will hog server resources, so he decides to enforce a policy of no more than three pending server calls at any time. Thus, the browser script pushes messages into a queue and tracks the progress of each XMLHttpRequest Call (Chapter 6) containing those commands. He can enforce his maximum call count policy by tracking the state of each call.

10.1.2. Problem

How can you control parallel XMLHttpRequest Calls (Chapter 6)?

10.1.3. Forces

  • Fast users and busy applications often stretch networks and servers to their limits.

  • Asynchronous interaction is the only practical approach for Ajax applications. You don't want to block all interaction just because the network happens to be slow.

  • The number of simultaneous requests must be controlled, because browsers can only handle a few at a time, and also to reduce the overall load.

10.1.4. Solution

Track XMLHttpRequest calls as they proceed from browser to server and back again. XMLHttpRequest is a fairly basic component that needs to be augmented or wrapped for better control over asynchronous dialogue. Furthermore, it is useful to keep all these wrappers in a collection. Note that this pattern is fairly low level, and the details should generally be encapsulated in a wrapper library.

The standard mechanism for Call Tracking requires an XMLHttpRequest wrapper. Consider the implementation of the Ajax Client Engine (ACE) library (http://www.lishen.name/). A Requester abstraction creates an XMLHttpRequest object upon construction:

   function Requester( )   {     var requester;     if (window.XMLHttpRequest)       {         requester = new window.XMLHttpRequest( );         ...       }     ...   } 

Requester's response handler doesn't go directly back to the caller's registered event handler, but instead to a method of Requester. This internal callback can then perform logging and other tasks before passing control back to the caller's handler. There's also a caching mechanism, so the Requester will add and remove itself from the cache as the call progresses.

The most important reason to track calls is to tame the asynchronous nature of XMLHttpRequest. There's a potential bug in which the programmer treats XMLHttpRequest as a Singleton (Gamma et al., 1995). That is, there's only a single, global, XMLHttpRequest instance, but parallel calls are made (http://smokey.rhs.com/web/blog/PowerOfTheSchwartz.nsf/d6plinks/RSCZ-6CDPEX). An initial call will be pending, when suddenly a new call steps in. What happens here is unclear, as it's the sort of unanticipated behavior that will vary across browser implementations, but it certainly won't be a happy result. The first call will possibly be cancelled, and it's unlikely its event handler will be notified. The simplest way to resolve this problem is to create a new XMLHttpRequest instance each time, then use a closure; i.e.:

   xhReq.onreadystatechange = function( ) {       if (xhReq.readyState != 4) { return; }       var serverResponse = xhReq.responseText;       ...   }; 

We can consider this a limited form of Call Tracking, since we've at least ensured each pending call will have its own associated XMLHttpRequest. Still, we can achieve a lot more by wrapping requests and placing the wrappers in a collection, as demonstrated by ACE and other libraries:


Pooling

A collection of wrappers can be used as a cache. When each object has delivered its response, it's returned to the available pool (and its state reset in case of an error). This reduces object creation and destruction.


Limiting

Using a fixed-size pool (see the previous point), you have the option of limiting the number of simultaneous requests to reduce network load and prevent problems in the browser; most browsers will only allow a handful of pending requests at any time.


Detecting timeouts

As explained in XMLHttpRequest Call (Chapter 6), the framework can start a timer when the request is issued and notify the caller if a timeout occurs.


Sequencing

With all requests coordinated by a central piece of logic, it's possible to influence the sequence of calls going in and out of the server. While it's best to design for calls to be completely parallel, sometimes you might have constraints; e.g., you might want the server to process calls in the same order as the user initiates them; i.e., fire a request only when the previous response has been received.


Atomic Processing

Related to the previous point, you often need to ensure the browser will handle one response completely before moving onto the next. One scenario is appending a bunch of response informationif the responses are dealt with in parallel, you'll end up with interleaved messages. One style of Call Tracking is to create JavaScript Command (Gamma et al.) objects from each response, push them onto a queue, and have a separate thread execute them one at a time.


Passing in Call Context

Sometimes, a service's response is minimal and doesn't include any detail about the original callfor example, false as opposed to <spellcheck term="misspeld"> false </spellcheck>. Indeed, sometimes there's no response at all, and the framework will need to inform the response handler of a timeout. In these cases, it's useful to provide some context about the original call. By tracking the call, a framework can remember a "call context" that the caller sets when the request is issued. When the framework eventually transmits the response to the caller's response handler, it also passes in the call context. See "Code Refactoring: AjaxPatterns Predictive Fetch Sum" in Predictive Fetch (Chapter 13) to see how a calling context can be used.


Logging

A wrapper can register itself as the wrapped object's response handler, and then log any changes. It can also poll for any new content (since there's no guarantee the event handler will be called when new content is added).

10.1.5. Real-World Examples

10.1.5.1. Ajax Client Engine (ACE) library

Li Shen's Ajax Client Engine (ACE) (http://www.lishen.name/) uses Call Tracking to ease development and harden the application in production. Among its many features are several related to Call Tracking:

  • Long calls are timed out.

  • Changes to XMLHttpRequest's response state are logged.

  • A service can be periodically polled.

  • The caller can declare exactly when the callback method should be invoked.

10.1.5.2. AjaxCaller library

The AjaxCaller library (http://ajaxify.com/run/Lib/js/ajaxCaller.js), used throughout the AjaxPatterns demos for Web Remoting, uses Call Tracking to pool Calls, which wrap XMLHttpRequest objects.

10.1.5.3. libXmlRequest library

libXmlRequest (http://www.whitefrost.com/reference/2005/09/09/libXmlRequest.html) is another XMLHttpRequest wrapper. It keeps XMLHttpRequest objects in a pool so they can be reused, and tracks the response status in order to manage the pool.

10.1.6. Code Example: Ajax Client Engine (ACE)

In this example, we'll look at ACE's internal handling of call timeouts. The library consists of Requester objects, which wrap XMLHttpRequest objects. As explained earlier in the Solution, the wrapped XMLHttpRequest objects are created upon construction. When the wrapper is invoked to make a call, it creates a timer to cancel the request if it takes too long. The timer ID is held as an attribute of the wrapper.

   timeoutId = window.setTimeout(endRequest, request.callbackTimeout * 1000); 

To track the call, the wrapper registers itself as a request handler:

   function beginRequest( )   {     ...     requester.onreadystatechange = readystatechangeHandler;     ...   } 

Its handler is therefore called upon each change, and in the case of a complete call, cancels the timer:

   function readystatechangeHandler( )     ...     if (requester.readyState == Ace.ReadyState.Complete)     {       ...       if (requester.status == Ace.Status.OK)       {         ...         if (timeoutId)         {           window.clearTimeout(timeoutId);           timeoutId = undefined;         }         ...       }     ...     } 

10.1.7. Alternatives

10.1.7.1. Fire-and-forget

Some calls need no tracking because they are low-priority uploads of information to the server. For example, a chat app might reasonably ignore the results of uploading the user's messages because a separate thread is continuously polling for all recent messages. Another example would be uploading information to support Predictive Fetch (Chapter 13), where the worst case is simply the lost opportunity of a performance optimization. Ajaxian.com featured an interesting article on optimizing for this kind of request (http://www.ajaxian.com/archives/2005/09/ajaxian_fire_an.html).

10.1.7.2. Global XMLHttpRequest

You may be able to get away with a single, global XMLHttpRequest under certain conditions and with care. For example, you can use a lock to ensure there's only one pending call at each time (which is really a special case of Call Tracking). However, you risk forcing the user to endure long waiting periods.

10.1.8. Metaphor

Call Tracking is like tagging self-addressed envelopes before you send them away, so you can track them as they return.

10.1.9. Acknowledgments

This pattern was originally inspired by Richard Schwartz's caution against the familiar anti-pattern of working with a global XMLHttpRequest (http://smokey.rhs.com/web/blog/PowerOfTheSchwartz.nsf/d6plinks/RSCZ-6CDPEX).




Ajax Design Patterns
Ajax Design Patterns
ISBN: 0596101805
EAN: 2147483647
Year: 2007
Pages: 169

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net