Neil Anderson
Neil Anderson

Reputation: 13

Cancelling runaway JavaScript timeouts/intervals within a revealing module pattern

I have a fairly standardised revealing module pattern to create objects for use as instances. Sometimes, within these patterns there are timeouts or intervals that need to be cancelled in the case that the module is no longer being used or referenced within external code.

Simplified example of this pattern:

function test() {
    window.timer = maketimer();
}

function maketimer() {
    var cls, my;

    my = {
        increment: 0,
        timerid: null,

        exec_timer: function() {
            my.timerid = window.setInterval(my.time, 2000);
        },

        time: function() {
            console.log("timer: ", my.timerid, my.increment++);
        }
    },

    cls = {
        //...
    }

    my.exec_timer();

    return cls;
};

test();

// some time later...
test();

In the case that test is called twice, for whatever reason, the variable window.timer is replaced with a second instance of maketimer but the first instance timer continues to run.

A lot of the time, my modules are intrinsically linked to DOM nodes, and more often than not the DOM nodes are removed with the old instances, so I could in theory check for the non-existence of the node or its placement outside of the DOM, and then cancel the interval in this case.

This is far more generic however, and I would like to be able to deal with timeouts outside of the DOM environment.

Upvotes: 1

Views: 211

Answers (3)

Neil Anderson
Neil Anderson

Reputation: 13

Expanding on the answer given by @nils, I've created the below example for constructing a module which takes in a single DOM node and will only clear the previous instance/timer if the DOM node has already been used:

<div id="element1">[code here that may be updated with XHR]</div>
<div id="element2">[code here that may be updated with XHR]</div>
(function(window) {
    var timerInstances = [];

    window.maketimer = function(element) {
        var cls, my, a;

        // find instances where the passed element matches
        for (a = 0; a < timerInstances.length; a += 1) {
            if (timerInstances[a].element === element) {
                console.log("instance already exists for element", element, "destroying...");
                timerInstances[a].in.destroyInstance();
            }
        }

        my = {
            increment: 0,
            timerid: null,

            exec_timer: function() {
                my.timerid = window.setInterval(my.time, 2000);
            },

            time: function() {
                console.log("timer: ", my.timerid, my.increment++);
            },

            destroyInstance: function() {
                window.clearInterval(my.timerid);
            }
        },

        cls = {
            //...
        }

        my.exec_timer();

        timerInstances.push({
            'element': element,
            'in': my
        });

        return cls;
    }
})(window);

function test(element) {
    window.timer = maketimer(element);
}

test(document.getElementById("element1")); // produces 1 timer
test(document.getElementById("element1")); // cancels last, produces 1 timer
test(document.getElementById("element2")); // produces 1 timer

The identifying argument could be anything here - in this case it's a DOM node, but it could easily be a Number, String etc.

The accepted answer is very helpful and is still preferred if you wish to maintain the rule of having one instance at a time on the document.

Upvotes: 0

nils
nils

Reputation: 27214

In this case I would wrap the whole function in an IIFE that contains an instance variable. In it, you save the timer. And every time a new one is started, the old one is destroyed:

(function(window) {
    var timerInstance = null;

    window.maketimer = function() {
        var cls, my;

        if(timerInstance) {
            timerInstance.destroyInstance();
        }

        my = {
            increment: 0,
            timerid: null,

            exec_timer: function() {
                my.timerid = window.setInterval(my.time, 2000);
            },

            time: function() {
                console.log("timer: ", my.timerid, my.increment++);
            },
            destroyInstance: function() {
                window.clearInterval(my.timerid);
            }
        },

        cls = {
            //...
        }

        my.exec_timer();

        timerInstance = my;

        return cls;
    }
})(window);

function test() {
    window.timer = maketimer();
}

test();
test();

Just out of curiosity, why do you need to have the instance on a global variable? window.timer is pretty generic and could be overridden by other scripts.

Upvotes: 1

Chilli
Chilli

Reputation: 123

Try it: Update your code below..

var settimer;
function test() {
   clearTimeout(settimer);
   settimer= setTimeout(function () { 
      window.timer = maketimer(); 
   }, 100);   
}

Upvotes: 0

Related Questions