Chris Tolworthy
Chris Tolworthy

Reputation: 2096

setInterval (javaScript): are there known bugs?

99 times out of 100, this works perfectly:

function a(){
   setInterval("b()",1000); 
   updateText("still working");
}
function b(){
   timer++;
   updateText(timer);
}

Occasionally the first loop waits for 20 seconds to 2 minutes. Thereafter it runs perfectly. I know the timer can pause on Android phones (when the soft keyboard is shown). Are there other conditions that might delay setInterval?

Upvotes: 2

Views: 3781

Answers (2)

gion_13
gion_13

Reputation: 41533

Firstly, it is strongly advised you provide a callback(function) as the first argument and not a string, because that string is evaluated in the global scope and we all know that bad things happen when we use eval in js (related eval post : When is JavaScript's eval() not evil?).
So, your

setInterval("b()", 1000); 

should be rewritten as :

setInterval(b, 1000); 

or:

setInterval(function() { b(); }, 1000); 

I also recommend you use setTimeout to simulate a setInterval.

The main downfall of the setInterval function is that it executes a block of code every n milliseconds, regardless of the execution of the previous block of code.
So if for some reason a setInterval callback takes longer to execute than the delay provided, it will cause some stack overflows.

Let's take the following code for example :

function foo() {
    // this takes about 2 seconds to execute
    // .. code here
}
setInterval(foo, 1000);

This will actually freeze the browser because it will execute foo for an (almost) infinite number of times but it will never finish it.

The solution in this kind of case is to emulate the setInterval with setTimeout, in order to ensure that the callback has finished to execute before calling it again:

function foo() {
    // this takes about 2 seconds to execute
    // .. code here
}
function newSetInterval(callback, duration, callbackArguments) {
    callback.apply(this, callbackArguments);
    var args = arguments,
        scope = this;

    setTimeout(function() {
        newSetInterval.apply(scope, args);
    }, duration);
}
newSetInterval(foo, 1000);

Now, foo is called again only after the previous instance has finished the code execution.

I would apply the same thing to your code, in order to let the browser decide when it can execute the code, and not to force it to execute the block of code weather it's busy at that moment or not:

function a() {
   newSetInterval(b, 1000); 
   updateText("still working");
}
function b() {
   timer++;
   updateText(timer);
}
function newSetInterval(callback, duration, callbackArguments) {
    callback.apply(this, callbackArguments);
    var args = arguments,
        scope=this;

    setTimeout(function() {
        newSetInterval.apply(scope, args);
    }, duration);
}

If you're interested, I've rewritten the setInterval and clearInterval functions in order to use them anywhere, without taking care of stack overflows :

function setInterval(f, time) {
    setInterval.ids = setInterval.ids || {};
    setInterval.idCount = setInterval.idCount || 0;
    var that = this,
        id = setInterval.idCount++,
        // to prevent firefox bug that adds an extra element to the arguments
        l = arguments.length - 2;

    (function theFn() {
        // to prevent firefox bug that adds an extra element to the arguments
        var args = [].slice.call(arguments, 0, l);
        f.apply(this, args);
        setInterval.ids[id] = setTimeout.apply(this, [theFn, time].concat(args));
    }).apply(that, [].slice.call(arguments, 2, arguments.length));
    return id;
}


function clearInterval(id) {
    if(!setInterval.ids || !setInterval.ids[id]) {
        return false;
    }
    clearTimeout(setInterval.ids[id]);
    return true;
} 

Upvotes: 11

Chamika Sandamal
Chamika Sandamal

Reputation: 24312

try this,

setInterval(b, 1000);

or

setInterval(function(){
   timer++;
   updateText(timer);
}, 1000);

Upvotes: 1

Related Questions