Call Tracking
From Ajax Patterns
There's an archived version of this pattern available, taken from the Ajax Pattern book draft, showing roughly how it appeared before the page became publicly editable.
Evidence: 3/3
Tags: Asynchronous, Follow, Monitor, Parallel, Tracking, XMLHttpRequest
Contents |
|
In A Blink
TODO Incorporate circa-August Ajaxian.com discussions, PPK http://www.quirksmode.org/blog/archives/2005/09/xmlhttp_notes_a_1.html, Ajaxian fire-and-forget, "Vindicated" ajaxblog pos, more... This was the first full pattern and needs a lot of work!
Wait for each server response with a separate XMLHttpRequest.
TODO diagram showing several objects having issued a request and waiting for a response.
Developer Story
Dave is writing an Ajax chat client, and he's concerned the more noisy users will hog server resources, so he decides to enforce a policy of no more than three pending server calls at any time. So the browser script pushes messages into a queue, and tracks the progress of each XMLHttpRequest Call containing those commands. He can enforce his maximum call count policy by tracking the state of each XMLHttpRequest Calls.
Problem
How can you control parallel XMLHttpRequest Calls?
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.
- Simultaneous requests are useful for performance reasons.
- The number of simultaneous requests must be controlled, because browsers can only handle a few at a time, and also too reduce the overall load.
Solution
Track XMLHttpRequest calls as they proceed from browser to server and back again. XMLHttpRequest is a fairly basic component, and 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. 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. 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 behaviour 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 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 (previous point), you have the option of limiting the number of simultaneous requests to reduce network load and prevents problems in the browser; most browsers will only allow a handful of pending requests at any time.
Detecting timeouts
As explained in “XMLHttpRequest Call”, the framework can start a timer when the request is issued and notify the caller if a timeout occurs.
Sequencing
With all requests co-ordinated 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 initiated them, hence 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" which 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 the Code Example in Predictive Fetch to see how a calling context can be used.
Using closures for handlers is a natural way of expressing call context; the context is implicit in the closure.
Another way of building in call context is to create a dispatch function to monitors changes in all potential responses/respondees and dispatches calls as appropriate (see: interrupt table, computer architecture). Thus, instead of generating a long sequence of closures as handlers, a single function can try to track which event sources have changed. For XHR, the function could monitor the length of all active XHRs, and call the associate handler (with some kind of generated contextual information) when the length of a XHR response changes.
Logging
A wrapper can register itself as the wrapped object's response handler, 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).
Decisions
Will you pool XMLHttpRequest objects?
Real-World Examples
TODO expand
Code Examples
AjaxCaller
Alternatives
You may be able to get away with a single, global, XMLHttpRequest under certain conditions and with care.
In some circumstances, synchronous transfer may be the right choice. For example, if the order in which the calls are made is important, then using synchronous transfers would be a natural choice. TODO Expand.
Visual Metaphor
Call Tracking is like marking self-addressed envelopes before you send them away, so you can track which ones are outstanding anytime.
Related Patterns
Just because you can deal with frequent requests doesn't mean you should. If you've taken a modeless approach, these requests will not be showstoppers (literally). But they will nonetheless slow down loading of valuable information, making the application feel less responsive. Use SubmissionThrottling and ExplicitSubmission to cut down on the frequency of server requests.
Want to Know More?
Discussion of related issue: browser DOM has changed by the time a request returns. Other uses of call tracking include services to monitor phone calls.
Acknowledgements
TODO
Time your website with
WebWait - from the creator of AjaxPatterns.org
