ismaestro
ismaestro

Reputation: 8267

Javascript recursive loop over array with setTimeout?

I have this code in ES6:

function loopLettersEvery3Seconds() {
    const letters = ['a', 'b', 'c'];

    let offset = 0;
    letters.forEach((letter, index) => {
      setTimeout(() => {
        // I do something with the letter, nothing important
      }, 3000 + offset);
      offset += 3000;
      if (index === letters.length - 1) {
        loopLettersEvery3Seconds(letters);
      }
    });
}

This function loop over an array of letters and every 3 second I can do something with every letter. But the thing is that when this loop comes to an end and finish, I can't repeat again the process... The recursive call generates an stack overflow (Uncaught RangeError: Maximum call stack size exceeded) lol

Tell me how to do it! Thanks!!

BR

Upvotes: 1

Views: 1331

Answers (4)

Redu
Redu

Reputation: 26161

I guess a quasi recursive loop is best in this case;

var chars = ["a","b","c"];

function loop(a,i = 0){
  console.log(a[i%a.length]);
  setTimeout(loop,1000,a,++i);
}

loop(chars);

Upvotes: 0

Sean Kwon
Sean Kwon

Reputation: 907

You can do something like this. This is how you can simulate a setInterval via recursion and setTimeouts

var array = ['a', 'b', 'c']

function loopLettersEvery3Seconds(arr, index) {
    if (index === arr.length) return;
    //Do stuff with array
    setTimeout(loopLettersEvery3Seconds, 3000, arr, index+1)
}
loopLettersEvery3Seconds(array, 0)

This way, you don't have to deal with the synchronous nature of loops and instead, do every thing based on the callstack and the web api (is that the right term for timeouts?)

If you want it to loop forever, then do this

var array = ['a', 'b', 'c']

function loopLettersEvery3Seconds(arr, index) {
    if (index === arr.length) return loopLettersEvery3Seconds(arr, 0);

    setTimeout(loopLettersEvery3Seconds, 3000, arr, index+1)
}
loopLettersEvery3Seconds(array, 0)

Upvotes: 3

gyre
gyre

Reputation: 16779

You should probably be using setInterval instead of setTimeout, which allows you to avoid using an offset variable. Note that you can use the modulo (%) operator to make sure that your index "loops" as you increment it:

function loopLettersEvery3Seconds() {
    const letters = ['a', 'b', 'c']
    let index = 0
    
    setInterval(() => {
      console.log(letters[index])
      index = (index + 1) % letters.length
    }, 3000)
}

loopLettersEvery3Seconds()

Upvotes: 0

Bergi
Bergi

Reputation: 664395

forEach is synchronous. You're doing your recursive call immediately after setting up the last timeout, which obviously will lead to a stack overflow when the function is calling itself without a base case to stop at.

For an forever-running animation, you will want to put the "recursive" call inside the last timeout:

function loopLettersEvery3Seconds() {
  ['a', 'b', 'c'].forEach((letter, index) => {
    setTimeout(() => {
      // I do something with the letter, nothing important
      if (index === letters.length - 1) {
        loopLettersEvery3Seconds(letters);
      }
    }, 3000 * (index + 1));
  });
}

Upvotes: 1

Related Questions