10.2. Periodic RefreshAuto-Update, Polling, Sync, Synchronise, Sychronize, Real-Time Figure 10-2. Periodic Refresh10.2.1. Developer StoryDevi's coding up a ticket sales web site. For each event, she wants to keep the browser updated with the number of tickets remaining. Thus, she introduces a timer so that every 30 seconds, it calls a web service to pull down the latest sales stats. 10.2.2. ProblemHow can the application keep users informed of changes occurring on the server? 10.2.3. Forces
10.2.4. SolutionThe browser periodically issues an XMLHttpRequest Call to gain new information; e.g., one call every five seconds. The solution makes use of the browser's Scheduling (Chapter 7) capabilities to provide a means of keeping the user informed of latest changes. In its simplest form, a loop can be established to run the refresh indefinitely, by continuously issuing XMLHttpRequest Calls (Chapter 6): setInterval(callServer, REFRESH_PERIOD_MILLIS); Here, the callServer function will invoke the server, having registered a callback function to get the new information. That callback function will be responsible for updating the DOM according to the server's latest report. Conventional web apps, even most of those using XMLHttpRequest Calls, operate under a paradigm of one-way communication: the client can initiate communication with the server, but not vice versa. Periodic Refresh fakes a back-channel: it approximates a situation where the server pushes data to the client, so the server can effectively receive new information from the browser. Indeed, as some of the examples show, the server can also mediate between users in almost real-time. So Periodic Refresh can be used for peer-to-peer communication too. But before we get too carried away with Periodic Refresh, it's important to note that it's a serious compromise, for two key reasons:
So a key design objective must be to increase the average refresh period and reduce the content per refresh, while maintaining a happy user experience. One optimization is a timeout: the refreshes cease when the system detects the user is no longer active, according to a Timeout (Chapter 17) mechanism. You also want to make sure each refresh counts; it's wasteful to demand lots of updates if the network isn't capable of delivering them. Thus, the browser script can do some monitoring and dynamically adjust the period so it's at least long enough to cope with all incoming updates. Many of the Performance Optimization patterns can also be applied to Periodic Refreshsee the next section. 10.2.5. Decisions10.2.5.1. How long will the refresh period be?The refresh period can differ widely, depending on usage context. Broadly speaking, we can identify three categories of activity level:
Sometimes, the best solution uses multiple Periodic Refresh cycles in parallel, each with a frequency reflecting the user's needs. An interactive wiki, for example, might update news headlines every 10 minutes, online statuses of other users every minute, and content being edited every second. 10.2.6. Real-World Examples10.2.6.1. Lace ChatInstant messaging (or "online chat") applications pre-date the Web, and, unlike email and other services, web interfaces have never quite worked out, partly due to the fact that people don't enjoy frequent full-page refreshes. Ajax makes it possible to avoid a complete refresh by pulling down messages with an XMLHttpRequest Call (Chapter 6). Only the new messages need to be sent in each periodic response. In fact, there are several applications under development. Lace Chat (http://socket7.net/lace/), for example, is only a proof-of-concept, but provides good evidence that web-based chat is feasible. Every few seconds the messages update to show any new messages other users may have entered. When you post a message, it's also handled as an XMLHttpRequest Call. 10.2.6.2. Magnetic PoetryLike a wiki, Magnetic Poetry (http://www.broken-notebook.com/magnetic/) involves a shared workspace (Figure 10-3). In this case, users move tiles through the space, and the application updates once a second to reflect the new space. As of version 1.7, the entire tile set is sent each time, but there's the potential to compress the information by sending only recent tile positions. This enables two users to work on the area simultaneously, and one can even see a tile being dragged by a different user, like a low-frequency animation. Figure 10-3. Magnetic Poetry10.2.6.3. Claude Hussenet's portalPortals, by definition, display various kinds of information. And often that information is of a dynamic nature, requiring periodic updates. Claude Hussenet's portal (http://www.claudehussenet.com/portal/Welcome.do) contains several portlets:
In each case, the information is volatile and needs to be periodically updated, as is typical for many portlets. Also characteristic of portal applications is the relatively long refresh period, 15 minutes in this case. Each portlet contains a manual refresh too, for immediate results. 10.2.7. Code Examples10.2.7.1. LaceLace Chat (http://socket7.net/lace/) handles Periodic Refreshes to show all users' chat messages. The timer is set on startup to periodically call the get() function, which initiates the XMLHttpRequest Call (Chapter 6) for new messages: this.timerID = setInterval(function () { thisObj.get(true); }, interval); get() performs a straightforward query of the server: this.httpGetObj.open('POST', this.url, true); this.httpGetObj.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=UTF-8'); var thisObj = this; this.httpGetObj.onreadystatechange = function ( ) { thisObj.handleGet(system); }; this.httpGetObj.send(param); If the server has changed at all, the entire contents are returned. How does Lace know if the server has changed? Each time the server responds, it generates a hash for the contents. And when the browser next calls for a refresh, it includes the last hash as a parameter to the call. The server sends a full response only if the current hash differs from that specified by the browser: [lib_lace.php] function getMessageHash( ) { ... return md5(implode(file(LACE_FILE))); } [lace.php] $_hash = getMessageHash( ); if ($_hash == $hash) exit; // no change exit($_hash.'||||'.getFileContentsRaw( )); 10.2.8. Code Refactoring: AjaxPatterns Periodic TimeThe Basic Time Demo (http://ajaxify.com/run/time) requires the user to manually update the time display by clicking a button. We'll perform a quick refactoring to re-fetch the time from the server every five seconds (http://ajaxify.com/run/time/periodicRefresh). First, we'll create the function that initiates the server calls. Here, we already have two functions like that, one for each display. So, let's ensure we call both of those periodically: function requestBothTimes() { requestDefaultTime(); requestCustomTime( ); } Then, the Periodic Refresh is simply a matter of running the request every five seconds: function onLoad( ) { ... setInterval(requestBothTimes, 5000); ... } One more nicety: the function in setTimeout begins to run only after the initial delay period. So we're left with empty time displays for five seconds. To rectify that, requestBothTimes is also called on startup: function onLoad() { ... requestBothTimes( ); setInterval(requestBothTimes, 5000); ... } Now, the user sees the time almost as soon as the page is loaded. 10.2.9. Alternatives10.2.9.1. HTTP StreamingOne of the forces for this pattern is that HTTP connections tend to be short-lived. That's a tendency, not a requirement. As HTTP Streaming (Chapter 6) explains, there are some circumstances where it's actually feasible to leave the connection open. Streaming allows for a sequence of messages to be downloaded into the browser without the need for explicit polling. (Low-level polling still happens at the operating system and network levels, but that doesn't have to be handled by the web developer, and there's no overhead of starting and stopping an HTTP connection for each refresh, nor of starting and stopping the web service.) 10.2.10. Related Patterns10.2.10.1. Distributed EventsYou can use Distributed Events (see later in this chapter) to coordinate browser activity following a response. 10.2.10.2. Fat Client, Browser-Side Cache, GuesstimateA little work on performance issues can help make the system feel more responsive. Some of the performance optimization patterns help:
10.2.10.3. Submission ThrottlingSubmission Throttling (see the next pattern) also involves a periodic call to the server. The emphasis there is on uploading browser-side changes, whereas the present pattern focuses on downloading server-side changes. The two may be combined to form a general-purpose "synchronize" operation, as long as the update frequency is sufficient in both directions. This would improve performance by reducing the amount of overall traffic. The Wiki Demo (http://ajaxify.com/run/wiki) takes this approach. 10.2.10.4. HeartbeatIt's inevitable that users will leave their browser pointing at web sites they're not actually using. The rising popularity of tabbed browsingnow supported by all the major browsersonly exacerbates the problem. You don't want to keep refreshing the page for idle users, so use Heartbeat (Chapter 17) to detect whether the user is still paying attention. 10.2.11. MetaphorA movie is refreshed at subsecond intervals to provide the illusion of real-time activity. |