Browser-Server Dialogue - Solution for Call Tracking
(Page 2 of 4 )
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. Theres 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. Theres a potential bug in which the programmer treats XMLHttpRequest as a Singleton (Gamma et al., 1995). That is, theres 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 information--if 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
call--for 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).
Next: Real-World Examples of Call Tracking >>
More JavaScript Articles
More By O'Reilly Media
|
This article is excerpted from chapter 10 of the book Ajax Design Patterns, written by Michael Mahemoff (O'Reilly, 2006; ISBN: 0596101805). Check it out today at your favorite bookstore. Buy this book now.
|
|