1

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

ismaestro
  • 7,561
  • 8
  • 37
  • 50

4 Answers4

3

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)
Sean Kwon
  • 907
  • 6
  • 12
  • I think this is a more elegant way of doing the recursion. If you are curious, here is "your" code: https://github.com/Ismaestro/ismaelramos/commit/6217183fd0ffcb5c7aecbb760aceff01897136de – ismaestro Mar 02 '17 at 20:10
1

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));
  });
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

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()
gyre
  • 16,369
  • 3
  • 37
  • 47
0

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);
Redu
  • 25,060
  • 6
  • 56
  • 76