JavaScript Polling

By  on  

Polling with JavaScript is one of those ugly but important functions within advanced front-end user experience and testing practices.  Sometimes there isn't the event you can hook into to signify that a given task is complete, so you need to get your hands dirty and simply poll for it.  Polling with JavaScript isn't difficult but it's not easy either.  Let me show you a few implementations of JavaScript polling that you can add to your toolbox!

With Promises

Since the Promise API is implemented almost all browsers today, here's a polling implementation using them:

// The polling function
function poll(fn, timeout, interval) {
    var endTime = Number(new Date()) + (timeout || 2000);
    interval = interval || 100;

    var checkCondition = function(resolve, reject) {
        // If the condition is met, we're done! 
        var result = fn();
        if(result) {
            resolve(result);
        }
        // If the condition isn't met but the timeout hasn't elapsed, go again
        else if (Number(new Date()) < endTime) {
            setTimeout(checkCondition, interval, resolve, reject);
        }
        // Didn't match and too much time, reject!
        else {
            reject(new Error('timed out for ' + fn + ': ' + arguments));
        }
    };

    return new Promise(checkCondition);
}

// Usage:  ensure element is visible
poll(function() {
	return document.getElementById('lightbox').offsetWidth > 0;
}, 2000, 150).then(function() {
    // Polling done, now do something else!
}).catch(function() {
    // Polling timed out, handle the error!
});

The code is structured easy enough to read but it's mostly three-fold:  the conditional function which signals polling success, a conditional failure which hasn't timeout out, so we'll run again, or a failure which has run past timeout which should return an error.

Without Deferreds

If you aren't using Deferreds, no worry -- polling is just about the same:

function poll(fn, callback, errback, timeout, interval) {
    var endTime = Number(new Date()) + (timeout || 2000);
    interval = interval || 100;

    (function p() {
            // If the condition is met, we're done! 
            if(fn()) {
                callback();
            }
            // If the condition isn't met but the timeout hasn't elapsed, go again
            else if (Number(new Date()) < endTime) {
                setTimeout(p, interval);
            }
            // Didn't match and too much time, reject!
            else {
                errback(new Error('timed out for ' + fn + ': ' + arguments));
            }
    })();
}

// Usage:  ensure element is visible
poll(
    function() {
        return document.getElementById('lightbox').offsetWidth > 0;
    },
    function() {
        // Done, success callback
    },
    function() {
        // Error, failure callback
    }
);

The difference here is that there's no return value -- the callback functions take the place of the Deferred instance.

Polling isn't necessarily a consequence of async coding but it has definitely increased in usage and importance due to our desire to write async code.  During my time writing front-end functional tests with the Intern testing framework, I've used polling quite a bit both on the server and client sides.  This technique will always have its place so make sure you have a snippet like this available.

Recent Features

  • By
    Serving Fonts from CDN

    For maximum performance, we all know we must put our assets on CDN (another domain).  Along with those assets are custom web fonts.  Unfortunately custom web fonts via CDN (or any cross-domain font request) don't work in Firefox or Internet Explorer (correctly so, by spec) though...

  • By
    5 Awesome New Mozilla Technologies You&#8217;ve Never Heard Of

    My trip to Mozilla Summit 2013 was incredible.  I've spent so much time focusing on my project that I had lost sight of all of the great work Mozillians were putting out.  MozSummit provided the perfect reminder of how brilliant my colleagues are and how much...

Incredible Demos

  • By
    CSS Tooltips

    We all know that you can make shapes with CSS and a single HTML element, as I've covered in my CSS Triangles and CSS Circles posts.  Triangles and circles are fairly simply though, so as CSS advances, we need to stretch the boundaries...

  • By
    Morphing Elements Using MooTools and CSS

    Morphing an element between CSS classes is another great trick the MooTools JavaScript library enables you to do. Morphing isn't the most practical use of MooTools, but it's still a trick at your disposal. Step 1: The XHTML The block of content that will change is...

Discussion

  1. I would return {cancel: function () { clearTimeout(interval); }} to the non deferred option in order to provide superior control to the poller author.

  2. Alex C

    The code above does not work as is. In practice, fn returned value is async so that you get a success with an empty callback. I am trying to figure out how to do this properly…

    • Can you provide an example of the code not working? I’d love to update this!

    • xis19

      e.g. If the fn() is an ajax call, then this polling will not work because the return value is Deferred.

    • Can you provide some quick sample code so I can check it out?

  3. The second one can be simpler using just a timeInterval. Why to waste an unnecessary IIFE, and then also use a timeOut.

    Correct me if I am missing anything, or if this might behave differently to your second implementation:

    function pollThis (fn, cb, er, to, ti) {
        var startTime = (new Date()).getTime();
        var pi = window.setInterval(function(){
            if (Math.floor(((new Date).getTime() - startTime) / 1000) <= to) {
                if (fn()) {
                    cb();
                }
            } else {
                window.clearInterval(pi);
                er();
            }
        }, ti)
    }
    
  4. Willy Loman

    How would you even use one of these functions? They don’t appear to be valid JS to me.

  5. twmbx

    For anyone that lands here and actually needs an example of what @Alex C & @xis19 are talking about. The following gist should help.

    https://gist.github.com/twmbx/2321921670c7e95f6fad164fbdf3170e

  6. ben

    How would this work with multiple polling?

  7. V

    How are you going to cancel the polling when an external event occurs?

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!