10.1. Call TrackingAsynchronous, Follow, Monitor, Parallel, Tracking, XMLHttpRequest Figure 10-1. Call Tracking10.1.1. Developer StoryDave 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. ProblemHow can you control parallel XMLHttpRequest Calls (Chapter 6)? 10.1.3. Forces
10.1.4. SolutionTrack 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:
10.1.5. Real-World Examples10.1.5.1. Ajax Client Engine (ACE) libraryLi 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:
10.1.5.2. AjaxCaller libraryThe 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 librarylibXmlRequest (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. Alternatives10.1.7.1. Fire-and-forgetSome 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 XMLHttpRequestYou 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. MetaphorCall Tracking is like tagging self-addressed envelopes before you send them away, so you can track them as they return. 10.1.9. AcknowledgmentsThis 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). |