blog

Timeout loops

RE: Doug Crockford’s talk at http://developer.yahoo.com/yui/theater/video.php?v=crockonjs-3

In slide 43, there’s a fade out algorithm that looks like this. He’s using it to illustrate closure – in this case, the variables ‘level’ and ‘dom’:

function fade(id) {
    var dom = document.getElementById(id),
        level = 1;
    function step() {
        var h = level.toString(16);
        dom.style.backgroundColor = '#FFFF' + h + h;
        if (level < 15) {
            level += 1;
            setTimeout(step, 100);
        }
    }
    setTimeout(step, 100);
}

Now this struck me as strange - mutation within a recursive algorithm. This inspired me to do a couple of rewrites that avoid that, whilst providing some interesting abstractions that separate concerns.

1. Separating out the notion of a delayed function.
timedOut is a higher-order that wraps a function in a timeout loop. This makes the code feel more like a normal recursive algorithm. Notice the step(level + 1) replaces the mutation.

function timedOut(fn, delay) {
    return function() {
        var args = arguments;
        setTimeout(function() {
            return fn.apply(this, args);
        }, delay);
    };
}

function fade(id) {
    var dom = document.getElementById(id);
    var step = timedOut(function(level) {
        var h = level.toString(16);
        dom.style.backgroundColor = '#FFFF' + h + h;
        if (level < 15) {
            step(level + 1);
        }
    }, 100);
    step(1);
}

2. Separating out the notion of a timeout loop.
I really like this one. It clearly separates the rendering logic from the mechanism that recurses n times.

function timeoutLoop(fn, delay, start, end) {
    var t = function(current) {
        setTimeout(function() {
            fn(current);
            if (current < end) {
                t(current + 1);
            }
        }, delay);
    };
    t(start);
}

function fade(id) {
    var dom = document.getElementById(id);
    var step = function(level) {
        var h = level.toString(16);
        dom.style.backgroundColor = '#FFFF' + h + h;
    };
    timeoutLoop(step, 100, 1, 15);
}

Leave a Reply

*