Smart Polling
时间:2009-04-05 来源:cobrawgl
I’m working on the rich UI of a small web application which has a problem I need to solve: the data from the server which the UI is presenting could change at any time. The first iteration of the UI has a refresh button. Clicking this button sends an Ajax request to a resource on the server which responds with a JSON data structure, and the UI is updated with any changes in the new data. My business partner doesn’t like the refresh button; he questions why it’s there and states how annoying it is to press the button all the time. His suggestion is the rich UI should smartly poll the server for changes to the data, and update the UI automatically.
Intrigued by his idea, we continued our discussion leading to a definition for what it means to smartly poll a server’s resource:
- Use conditional GET requests
- Retain the most recent Etag and Last-Modified date of the polled resource
- Disable polling when the browser window is inactive
Implementing a smart polling process in our application’s rich UI gives us some desired benefits:
- Removal of the refresh button
- Automatic updating of the UI when the resource on the server has changed
- Less repainting of the page since the DOM is touched only when the data has changed
- Changes to the UI only happen when the window is active (the user sees them) as polling is paused while the user is doing something else
Creating a reusable component to achieve a smart polling process feels like the correct approach as I foresee a need to use this functionality in future projects as well.
Poller - YUI3 Component
The remainder of this post pertains to Poller, a YUI3 compatible component hosted in the files sections of this site: http://925html.com/files/smart_polling/ (a Bazaar branch)
I’ve been developing several components and widgets using the new YUI3 component infrastructure; this is the first fully built and documented (including docs for the relevant YUI modules) one I’m releasing.
- Poller API
- Poller Build
- Poller Src
The Poller class is 3.3kb minified and 1.1kb gzipped, it requires io-base and base YUI3 modules.
The Poller class’ usage is through the public API methods: pause, sendRequest, start and stop; along with events: modified, request, response, start and stop.
Internally the Poller class is using conditional XHR GET requests. When polling is first kicked off (i.e. start() is called) the Etag and Last-Modified header values are cached. As subsequent polling requests are sent to the server the cached Etag is set for the If-None-Match HTTP request header, if no Etag was cached (i.e. the server doesn’t send Etags) then the cached Last-Modified date is set as the If-Modified-Since header. If a server doesn’t support conditional GET requests at all, sending neither an Etag nor a Last-Modified date, the Poller class will fire the modified event with every response.
Example instance of Poller
var myData = new Poller({ url:'path/to/resource/', interval:5000 });
myData.on( 'modified', updateUI );
myData.start();
The above code will continue sending Ajax requests to the resource at path/to/resource/ until myData.stop() is called. If the Poller receives a 200 HTTP status (and not a 304 not modified status) the modified event will be fired passing the txId, response and args to the updateUI subscriber function. Subscribing to the poller:modified event is like subscribing to Y.io’s io:success event.
Disable polling on inactive windows
With the above functionality covering most of our requirements, we still need a way to pause polling when the browser window isn’t active. We came up with the idea to attach listeners to the window focus and blur events which call into protected methods to start/stop the polling process. Note: this functionality won’t interfere with the client code’s intent. i.e. if the client explicitly calls stop(), losing (blur) then gaining focus of the browser window won’t automatically start the polling process again. It respects the state of the Poller’s polling read-only attribute; calling start() will set the polling attribute to true and calling stop() will set the attribute to false.
To enable the functionality to pause and resume polling on the window’s focus and blur events: either set the pauseInactive attribute, or declare pauseInactive: true on the config object during construction.
Using the myData Poller instance from above:
myData.set( 'pauseInactive', true );
Setting the paseInactive attribute on construction:
var myOtherData = new Poller({
url : 'path/to/resource/',
interval : 5000,
pauseInactive : true
});
Putting it all together
I have a small demo for this project which continually checks a resource on the server to see if it has changed and updates the page if it has. The resource is a super simple JSON object with two properties: label and time, where time is a timestamp indicating the time on the server when the file was last modified.
{ "label":"the server's time was", "time":1238530883810 }
There is a button on the page which will update the data file stamping the new time on the server as the value of the time property. You can press this button to update the data file and after the next polling request is sent the UI will be updated to reflect the change in the file. I’ve included a YUI Console on the page which I send log messages to so you can see what’s going on.
The following is the code pertaining to the Poller instance used in the demo:
var poller = new Y.Poller({
url : 'data.json',
headers : {"foo":"bar"},
interval : 7000,
pauseInactive : true
});
poller.on( 'request', logRequestData );
poller.on( 'response', logResponseData );
poller.on( 'modified', updateUI );
poller.start();
Run The Demo
Note: I’ve have some issues running the demo on IE8 and IE7, it has something to do with the YUI Console; enabling the Developer Tools in IE8 made the demo work correctly.