Changing the interval of SetInterval while it's running
Asked Answered
C

17

200

I have written a javascript function that uses setInterval to manipulate a string every tenth of a second for a certain number of iterations.

function timer() {
    var section = document.getElementById('txt').value;
    var len = section.length;
    var rands = new Array();

    for (i=0; i<len; i++) {
        rands.push(Math.floor(Math.random()*len));
    };

    var counter = 0
    var interval = setInterval(function() {
        var letters = section.split('');
        for (j=0; j < len; j++) {
            if (counter < rands[j]) {
                letters[j] = Math.floor(Math.random()*9);
            };
        };
        document.getElementById('txt').value = letters.join('');
        counter++

        if (counter > rands.max()) {
            clearInterval(interval);
        }
    }, 100);
};

Instead of having the interval set at a specific number, I would like to update it every time it runs, based on a counter. So instead of:

var interval = setInterval(function() { ... }, 100);

It would be something like:

var interval = setInterval(function() { ... }, 10*counter);

Unfortunately, that did not work. It seemed like "10*counter" equals 0.

So, how can I adjust the interval every time the anonymous function runs?

Concoct answered 14/8, 2009 at 21:14 Comment(0)
C
133

Use setTimeout() instead. The callback would then be responsible for firing the next timeout, at which point you can increase or otherwise manipulate the timing.

EDIT

Here's a generic function you can use to apply a "decelerating" timeout for ANY function call.

function setDeceleratingTimeout(callback, factor, times)
{
    var internalCallback = function(tick, counter) {
        return function() {
            if (--tick >= 0) {
                window.setTimeout(internalCallback, ++counter * factor);
                callback();
            }
        }
    }(times, 0);

    window.setTimeout(internalCallback, factor);
};

// console.log() requires firebug    
setDeceleratingTimeout(function(){ console.log('hi'); }, 10, 10);
setDeceleratingTimeout(function(){ console.log('bye'); }, 100, 10);
Cormier answered 14/8, 2009 at 21:18 Comment(12)
By callback, do you mean the last line of the function calls itself recursively with a setTimeout(..., newInterval) ?Joanajoane
I assume that is what he meant. I just tried that and it seems to be working. Thanks, guys!Concoct
@joeydi, agreed. I just wanted to state it in case others dont! ;)Joanajoane
Added an example of a re-usable callableCormier
only gives 9 hi's :) --t should probably be t-- jsfiddle.net/albertjan/by5fdBobbinet
"exponential backoff" might be the key term here? Or I guess a little tweak might flip it over to achieve that functionality..Oleg
Doing this recursively don't you run the risk of getting a stack overflow error if times is too large?Wenoa
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Ikhnaton
Being nitpicky here, but I gotta say, that code is pretty hard to read. If you're gonna use next-line braces, at least have the decency to either use 4-8-space indentation or never go beyond 2 indents. IMO this version is much easier to read. Also take note of the renaming of t to tick, which was my best guess as to whatever "t" is supposed to stand for. t is a pretty bad variable name.Gradeigh
Also, if you're wondering what I'm doing here, I found this question while looking up how to synchronize a function call with the system clock. JS doesn't seem to support that, so I ended up rolling my ownGradeigh
now I know why I was confused.. internalCallback is a self calling function. that (times, 0) had me confused because there were no () just before it, instead {}.Demandant
@AndréChristofferAndersen that's my concern as well. they're just deepening the stack with each call. i'm doing an interval of around 500ms for multi hour long periods. The stack will get enormous!Hydrastinine
A
162

You could use an anonymous function:

var counter = 10;
var myFunction = function(){
    clearInterval(interval);
    counter *= 10;
    interval = setInterval(myFunction, counter);
}
var interval = setInterval(myFunction, counter);

UPDATE: As suggested by A. Wolff, use setTimeout to avoid the need for clearInterval.

var counter = 10;
var myFunction = function() {
    counter *= 10;
    setTimeout(myFunction, counter);
}
setTimeout(myFunction, counter);
Alf answered 16/9, 2011 at 14:1 Comment(7)
Well RozzA, my answer was posted on Sep 16 '11 and user28958's on Aug 22 '13, so I'll take the "rep" thanks!Alf
Why are you using an interval, a simple timeout would be better without the need to clear it. e.g: jsfiddle.net/fgs5nwgnDoralia
I was sticking to the context of the question. setTimeout will work of courseAlf
@A.Wolff Just a note, you don't need to define the timeout variables... They aren't doing anythingDextrad
And how can stop loop the second code?Jolson
@NabiK.A.Z. You just need to wrap the setTimeout (inside the function) in an if statement. Here's a detailed example with explanations of how it might be used, jsfiddle.net/q4c2rk1aPsalms
@A.Wolff I thought that not using clearTimeout() or clearInterval() could cause memory leaks. Isn't that right? I may be wrong but the usage of recursivity and absence of return without the appropriate clear function looks strange. Maybe it's a thing to consider.Leslie
C
133

Use setTimeout() instead. The callback would then be responsible for firing the next timeout, at which point you can increase or otherwise manipulate the timing.

EDIT

Here's a generic function you can use to apply a "decelerating" timeout for ANY function call.

function setDeceleratingTimeout(callback, factor, times)
{
    var internalCallback = function(tick, counter) {
        return function() {
            if (--tick >= 0) {
                window.setTimeout(internalCallback, ++counter * factor);
                callback();
            }
        }
    }(times, 0);

    window.setTimeout(internalCallback, factor);
};

// console.log() requires firebug    
setDeceleratingTimeout(function(){ console.log('hi'); }, 10, 10);
setDeceleratingTimeout(function(){ console.log('bye'); }, 100, 10);
Cormier answered 14/8, 2009 at 21:18 Comment(12)
By callback, do you mean the last line of the function calls itself recursively with a setTimeout(..., newInterval) ?Joanajoane
I assume that is what he meant. I just tried that and it seems to be working. Thanks, guys!Concoct
@joeydi, agreed. I just wanted to state it in case others dont! ;)Joanajoane
Added an example of a re-usable callableCormier
only gives 9 hi's :) --t should probably be t-- jsfiddle.net/albertjan/by5fdBobbinet
"exponential backoff" might be the key term here? Or I guess a little tweak might flip it over to achieve that functionality..Oleg
Doing this recursively don't you run the risk of getting a stack overflow error if times is too large?Wenoa
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Ikhnaton
Being nitpicky here, but I gotta say, that code is pretty hard to read. If you're gonna use next-line braces, at least have the decency to either use 4-8-space indentation or never go beyond 2 indents. IMO this version is much easier to read. Also take note of the renaming of t to tick, which was my best guess as to whatever "t" is supposed to stand for. t is a pretty bad variable name.Gradeigh
Also, if you're wondering what I'm doing here, I found this question while looking up how to synchronize a function call with the system clock. JS doesn't seem to support that, so I ended up rolling my ownGradeigh
now I know why I was confused.. internalCallback is a self calling function. that (times, 0) had me confused because there were no () just before it, instead {}.Demandant
@AndréChristofferAndersen that's my concern as well. they're just deepening the stack with each call. i'm doing an interval of around 500ms for multi hour long periods. The stack will get enormous!Hydrastinine
L
24

I like this question - inspired a little timer object in me:

window.setVariableInterval = function(callbackFunc, timing) {
  var variableInterval = {
    interval: timing,
    callback: callbackFunc,
    stopped: false,
    runLoop: function() {
      if (variableInterval.stopped) return;
      var result = variableInterval.callback.call(variableInterval);
      if (typeof result == 'number')
      {
        if (result === 0) return;
        variableInterval.interval = result;
      }
      variableInterval.loop();
    },
    stop: function() {
      this.stopped = true;
      window.clearTimeout(this.timeout);
    },
    start: function() {
      this.stopped = false;
      return this.loop();
    },
    loop: function() {
      this.timeout = window.setTimeout(this.runLoop, this.interval);
      return this;
    }
  };

  return variableInterval.start();
};

Example use

var vi = setVariableInterval(function() {
  // this is the variableInterval - so we can change/get the interval here:
  var interval = this.interval;

  // print it for the hell of it
  console.log(interval);

  // we can stop ourselves.
  if (interval>4000) this.stop();

  // we could return a new interval after doing something
  return interval + 100;
}, 100);  

// we can change the interval down here too
setTimeout(function() {
  vi.interval = 3500;
}, 1000);

// or tell it to start back up in a minute
setTimeout(function() {
  vi.interval = 100;
  vi.start();
}, 60000);
Levinson answered 14/8, 2009 at 22:24 Comment(2)
Thanks - set me off in the right direction for something similar I'm working on.Hamhung
Simple and effective. Thanks!Shipley
D
24

I had the same question as the original poster, did this as a solution. Not sure how efficient this is ....

let interval = 5000; // initial condition
let run = setInterval(request, interval); // start setInterval as "run"

function request() {

    console.log(interval); // firebug or chrome log
    clearInterval(run); // stop the setInterval()

    // dynamically change the run interval
    if (interval > 200) {
        interval = interval * .8;
    } else {
        interval = interval * 1.2;
    }

    run = setInterval(request, interval); // start the setInterval()
}
Deeannadeeanne answered 22/8, 2013 at 6:26 Comment(2)
i like this answer better because it actually answers OP (and my) question. setTimeout is subject to being delayed (by 100% cpu use, other scripts, etc) where as setInterval IS NOT affected by those delays--making it far superior for 'realtime' stuffCollegian
I'm 99% sure that your claim about setInterval is wrong @Collegian - It is still subject to the same delays as any other JavaScript, and almost every browser also clamps setInterval to 4ms. Do you have a link to a post about this or something?Levinson
C
16

This is my way of doing this, i use setTimeout:

var timer = {
    running: false,
    iv: 5000,
    timeout: false,
    cb : function(){},
    start : function(cb,iv){
        var elm = this;
        clearInterval(this.timeout);
        this.running = true;
        if(cb) this.cb = cb;
        if(iv) this.iv = iv;
        this.timeout = setTimeout(function(){elm.execute(elm)}, this.iv);
    },
    execute : function(e){
        if(!e.running) return false;
        e.cb();
        e.start();
    },
    stop : function(){
        this.running = false;
    },
    set_interval : function(iv){
        clearInterval(this.timeout);
        this.start(false, iv);
    }
};

Usage:

timer.start(function(){
    console.debug('go');
}, 2000);

timer.set_interval(500);

timer.stop();
Clint answered 30/6, 2015 at 14:0 Comment(4)
+1, I modified it slightly for my purposes so I can use multiple variable intervals - jsfiddle.net/h70mzvdqPhenosafranine
Also modified the set_interval function to NOT kick off a new execution unless the new interval is smaller than the old one.. if (iv < this.iv) { clearInterval(this.timeout); this.start(false, iv); } else { this.iv = iv; }Phenosafranine
I also liked this solution, but preferred if the timer did not get changed unless it was a 1/2 second different from the last. I modified set_interval function to be: let round = Math.trunc( iv / 500) * 500; if (round != this.iv ) { clearInterval( this.timeout ); this.start( false, round ); }Dine
I modified this for my use-case too.Mccusker
L
13

A much simpler way would be to have an if statement in the refreshed function and a control to execute your command at regular time intervals . In the following example, I run an alert every 2 seconds and the interval (intrv) can be changed dynamically...

var i=1;
var intrv=2; // << control this variable

var refreshId = setInterval(function() {
  if(!(i%intrv)) {
    alert('run!');
  }
  i++;
}, 1000);
Lorient answered 10/6, 2011 at 17:11 Comment(3)
This is my personal favorite too. small, simple, extensible.Arathorn
I wanted a solution of a decelerating timer that could have its rate reset based on application events; this met that need simply and perfectly. Thank you.Kindergarten
It's cool but it also fires intervals at moments that you don't need it... also it's a bit unreadable. For these reasons I'd prefer setTimeout personally.Leery
M
4

This can be initiated however you want. timeout is the method i used to keep it on the top of the hour.

I had the need for every hour to begin a code block on the hour. So this would start at server startup and run the interval hourly. Basicaly the initial run is to begin the interval within the same minute. So in a second from init, run immediately then on every 5 seconds.

var interval = 1000;
var timing =function(){
    var timer = setInterval(function(){
        console.log(interval);
        if(interval == 1000){ /*interval you dont want anymore or increment/decrement */
            interval = 3600000; /* Increment you do want for timer */
            clearInterval(timer);
            timing();
        }
    },interval);
}
timing();

Alternately if you wanted to just have something happen at start and then forever at a specific interval you could just call it at the same time as the setInterval. For example:

var this = function(){
 //do
}
setInterval(function(){
  this()
},3600000)
this()

Here we have this run the first time and then every hour.

Marty answered 24/9, 2015 at 15:45 Comment(0)
P
2

I couldn't synchronize and change the speed my setIntervals too and I was about to post a question. But I think I've found a way. It should certainly be improved because I'm a beginner. So, I'd gladly read your comments/remarks about this.

<body onload="foo()">
<div id="count1">0</div>
<div id="count2">2nd counter is stopped</div>
<button onclick="speed0()">pause</button>
<button onclick="speedx(1)">normal speed</button>
<button onclick="speedx(2)">speed x2</button>
<button onclick="speedx(4)">speed x4</button>
<button onclick="startTimer2()">Start second timer</button>
</body>
<script>
var count1 = 0,
    count2 = 0,
    greenlight = new Boolean(0), //blocks 2nd counter
    speed = 1000,   //1second
    countingSpeed;
function foo(){
    countingSpeed = setInterval(function(){
        counter1();
        counter2();
    },speed);
}
function counter1(){
    count1++;
    document.getElementById("count1").innerHTML=count1;
}
function counter2(){
    if (greenlight != false) {
        count2++;
        document.getElementById("count2").innerHTML=count2;
    }
}
function startTimer2(){
    //while the button hasn't been clicked, greenlight boolean is false
    //thus, the 2nd timer is blocked
    greenlight = true;
    counter2();
    //counter2() is greenlighted
}

//these functions modify the speed of the counters
function speed0(){
    clearInterval(countingSpeed);
}
function speedx(a){
    clearInterval(countingSpeed);
    speed=1000/a;
    foo();
}
</script>

If you want the counters to begin to increase once the page is loaded, put counter1() and counter2() in foo() before countingSpeed is called. Otherwise, it takes speed milliseconds before execution. EDIT : Shorter answer.

Proclivity answered 6/3, 2014 at 21:13 Comment(0)
I
2
(function variableInterval() {
    //whatever needs to be done
    interval *= 2; //deal with your interval
    setTimeout(variableInterval, interval);
    //whatever needs to be done
})();

can't get any shorter

Ingunna answered 9/1, 2018 at 15:51 Comment(0)
T
2

Here is yet another way to create a decelerating/accelerating interval timer. The interval gets multiplied by a factor until a total time is exceeded.

function setChangingInterval(callback, startInterval, factor, totalTime) {
    let remainingTime = totalTime;
    let interval = startInterval;

    const internalTimer = () => {
        remainingTime -= interval ;
        interval *= factor;
        if (remainingTime >= 0) {
            setTimeout(internalTimer, interval);
            callback();
        }
    };
    internalTimer();
}
Townsend answered 6/2, 2019 at 17:27 Comment(0)
C
2

You can do this by clearing the interval every iteration, changing the timer value and setting the interval again. Hope it helps ;)

For exemple:

const DOMCounter = document.querySelector(".counter")

let timer = 1000

const changeCounter = () => {
  clearInterval(interval)
  DOMCounter.innerHTML = timer
  timer += 1000
  timer == 5000 && timer == 1000
  interval = setInterval(changeCounter, timer)
}

let interval = setInterval(changeCounter, timer)
<div class="container">
  <p class="counter"></p>
</div>
Creese answered 28/10, 2022 at 14:27 Comment(0)
C
1

This piece of code below accelerates (acceleration > 1) or decelerates (acceleration <1) a setInterval function :

function accelerate(yourfunction, timer, refresh, acceleration) {
    var new_timer = timer / acceleration;
    var refresh_init = refresh;//save this user defined value
    if (refresh < new_timer ){//avoid reseting the interval before it has produced anything.
        refresh = new_timer + 1 ;
    };
    var lastInter = setInterval(yourfunction, new_timer);
    console.log("timer:", new_timer);
    function stopLastInter() {
        clearInterval(lastInter);
        accelerate(yourfunction, new_timer, refresh_init, acceleration);
        console.log("refresh:", refresh);
    };
    setTimeout(stopLastInter, refresh);
}

With :

  • timer: the setInterval initial value in ms (increasing or decreasing)
  • refresh: the time before a new value of timer is calculated. This is the step lenght
  • acceleration: the gap between the old and the next timer value. This is the step height
Cuirassier answered 22/12, 2016 at 14:34 Comment(5)
i'm blind or there is no factor in your code?Braasch
also, how to stop it?Braasch
Thx. I corrected the answer: factor was the old name for acceleration...it's clearer now! sorry for that. About "how to stop it": i would pass a var (continue = true) to the accelerate function, and add a first line in the accelerate function : while (continue) {Cuirassier
I suggest that to stop, you return a function that clear the interval and the timeout, and when it calls the accelerate again, you store the function that it returns to call it tooBraasch
I created a pen here with my changes: codepen.io/Alynva/pen/vYJdwQY?editors=0011Braasch
M
1

Make new function:

// set Time interval
$("3000,18000").Multitimeout();

jQuery.fn.extend({
    Multitimeout: function () {
        var res = this.selector.split(",");
        $.each(res, function (index, val) { setTimeout(function () { 
            //...Call function
            temp();
        }, val); });
        return true;
    }
});

function temp()
{
    alert();
}
Monumental answered 18/6, 2018 at 10:10 Comment(0)
P
0

Inspired by the internal callback above, i made a function to fire a callback at fractions of minutes. If timeout is set to intervals like 6 000, 15 000, 30 000, 60 000 it will continuously adapt the intervals in sync to the exact transition to the next minute of your system clock.

//Interval timer to trigger on even minute intervals
function setIntervalSynced(callback, intervalMs) {

    //Calculate time to next modulus timer event
    var betterInterval = function () {
        var d = new Date();
        var millis = (d.getMinutes() * 60 + d.getSeconds()) * 1000 + d.getMilliseconds();
        return intervalMs - millis % intervalMs;
    };

    //Internal callback
    var internalCallback = function () {
        return function () {
            setTimeout(internalCallback, betterInterval());
            callback();
        }
    }();

    //Initial call to start internal callback
    setTimeout(internalCallback, betterInterval());
};
Pragmatics answered 3/3, 2020 at 22:34 Comment(0)
W
0

You can use a variable and change the variable instead.

setInterval(() => function, variable)
Winkle answered 22/10, 2020 at 3:24 Comment(0)
J
0

This is my idea for times when you do not want loops like setInterval to overlap.
You also want to be able to set the loop execution delay and start and stop the loop, instansly on the fly.
I am using a loop_flag variable and a setTimeout function.
I set the main function to async so that you can call other functions in the body by calling await. When the main body of your code is running, the main loop waits and does not repeat itself. (which is not the case with setInterval)

An example of a simple code is:

//@NabiKAZ

document.getElementById("btn_start").addEventListener("click", function() {
  console.log("Starting...");
  loop_flag = true;
  loop_func();
});
document.getElementById("btn_stop").addEventListener("click", function() {
  console.log("Stoping...");
  loop_flag = false;
});

var n = 0;

var loop_flag = false;
var loop_func = async function() {
  if (!loop_flag) {
    console.log("STOP.");
    return;
  }

  //body main function inhere
  n++;
  console.log(n);
  ////


  if (loop_flag) {
    setTimeout(loop_func, document.getElementById("inp_delay").value);
  } else {
    console.log("STOP.");
  }
}
<input id="inp_delay" value="1000">
<button id="btn_start">START</button>
<button id="btn_stop">STOP</button>

For a more complete code with a fetch request inside the loop, see here:

https://jsfiddle.net/NabiKAZ/a5hdw2bo/

Jolson answered 5/3, 2022 at 3:4 Comment(0)
K
-1
var counter = 15;
var interval = function() {
    setTimeout(function(){
        // Write your code here and remove console.log, remember that you need declare yourDynamicValue and give it a value
        console.log((new Date()).getTime())

        window.counter = yourDynamicValue;
        window.interval();
    }, counter);
}

// It needs to run just once as init

interval();

Kerenkeresan answered 25/4, 2013 at 8:18 Comment(1)
give me error: Uncaught TypeError: interval is not a function but this worked: jsfiddle.net/fgs5nwgnJolson

© 2022 - 2024 — McMap. All rights reserved.