11

I am trying to loop through an array, but want to output each value of the array with a delay. This is what my current understanding is on how it should work:

EDIT

Requested JS Fiddle: http://jsfiddle.net/d3whkjww/

    loopThroughSplittedText: function(splittedText) {

        for (var i = 0; i < splittedText.length; i++) {
            // for each iteration console.log a word
            // and make a pause after it
            setTimeout(
                console.log(splittedText[i]),
                1000
            );
        };

    },

Yet, it does not work, and I believe it might be, because the arguments in the "for" loop have to be inside the setTimeout function. Yet I don't know how to make it work.

All I get is every value of the array at once, but I want them appear with a delay. How do I do that?

LoveAndHappiness
  • 9,735
  • 21
  • 72
  • 106
  • Can you provide us with a Fiddle, so that people can test with your data? – YaBCK Jun 16 '15 at 10:50
  • you have to increase the `duration` of the timeout. – Daniel A. White Jun 16 '15 at 10:54
  • @Jamiec: I think the dupe was meant to be about the issue of using `setTimeout` in the code supplied, but it seems the OP is immediately calling instead of passing a function as the first parameter. – Qantas 94 Heavy Jun 16 '15 at 10:54
  • I've looked at your Fiddle and made an example for you using your array. In my example it will show you how you can use a timer to create an array. This is good because you then use the timer in other part of your code with out having to keep on typing out the code – YaBCK Jun 16 '15 at 11:20

13 Answers13

13
var splittedText = ["Hello", "World", "How", "Are", "You", "Today"];

function loopThroughSplittedText(splittedText) {
    for (var i = 0; i < splittedText.length; i++) {
        // for each iteration console.log a word
        // and make a pause after it
        (function (i) {
            setTimeout(function () {
                document.getElementById('text').innerHTML += splittedText[i];
                console.log(splittedText[i]);
            }, 1000 * i);
        })(i);
    };
}
loopThroughSplittedText(splittedText);

Fiddle Demo

nan
  • 3
  • 2
Jashwant
  • 28,410
  • 16
  • 70
  • 105
8

Chances are you're going to want to use a recursive function instead of a for loop here. However, I'll explain both ways just in case you (or someone else reading this) has your heart set on doing this with a loop.

For a recursive function, the general idea is that you'll want to call the function once, then let it call itself repeatedly until it's finished doing what you want it to do. In terms of code, it will could look something a bit like this:

loopThroughSplittedText: function(splittedText) {

  // Create our counter; delayedOutput will use this to
  // track how far along in our string we are currently at
  var locationInString = 0;

  function delayedOutput() {

    // Output the next letter in our string
    console.log(splittedText[locationInString]);

    // Increment our counter so that on the next call we are on the next letter
    locationInString++;

    // Only perform setTimeout if we still have text left to output
    if (locationInString < splittedText.length) {

      // Functions can reference themselves using their own name
      setTimeout(delayedOutput, 1000);
    }
  }

  // Call our function once to get things started
  delayedOutput(); 
},

Alternatively, if you really prefer using a loop, you can still do it, but there's a fair bit of fiddling that has to be done to accomplish this.

First, you're going to need to place console.log within its own function. This is because when you place console.log(something), you're not actually passing it, but calling it right then and there, which is not what you want; by calling it, it spits out the text to the console right away rather than waiting until later. Tucking it away in its own function allows it to be passed to setTimeout so it can be called later on.

Second, you're going to have to wrap that function in yet another function to ensure that it's given the correct value of i when it fires. The reason is effectively this: Your intention is to tell the function "when you're ready, use what i was when I set you up." However, what you're doing right now is effectively saying "when you're ready, look at i". Because the function doesn't check what i is until it's ready to fire, it won't know its value until long after you have performed the loop, meaning i will be a number much higher than you want!

As a bit of a sub-point to the above, you'll want to call that function immediately. This is known as an immediately invoked function expression. If you're not familiar with them, they're certainly worth looking up. Their uses are a bit unusual, but they're a powerful tool in the right spot.

Finally, because you're setting up everything right here and now, you want to make sure the timeout for each function is a second apart; as it stands now, you're saying "do all of these one second from now", when your intention is "do all of these one second apart, starting one second from now". This fix is relatively easy; all you need to do is multiply your timeout by i so that you set up the first to go 1 second from now, the second to go 2 seconds from now, and so on.

All of that combined gives you code that looks something like this:

loopThroughSplittedText: function(splittedText) {

  for (var i = 0; i < splittedText.length; i++) {
    setTimeout(
      (function(locationInString) {
        return function() {
          console.log(splittedText[locationInString]);
        };
      }(i)),
      (1000*i)
    );
  }

},

As for which solution is better, I would probably recommend the recursive function. The recursive version will only create one function that calls itself for every string you pass it, whereas the for loop version will create one function for every character in the string, which could get out of hand very quickly. Function creation (and object creation in general) can get expensive in JavaScript when you're working on larger projects, so it's generally best to favor solutions that avoid creating massive amounts of functions when possible.

But still, for sake of explanation, I wouldn't want to leave you without the for loop version; the knowledge could come in handy in other places. :)

6

A recursive function call would do the job:

var a = [
    1,2,3,4,5,6,7,8,9,10
    ];

function log(i){
    console.log(a[i]);
    if (i<a.length){
       setTimeout(function(){
           i++;
           log(i);
       },1000);
    }
}

log(0);

http://jsfiddle.net/Curt/rjve4whe/1/

Curtis
  • 101,612
  • 66
  • 270
  • 352
4

In my example, it will show you how to loop through an array contentiously until you stop. This is to just give you an idea on how you can do the delay. Also it shows you when the value actually got displayed.

I would say that you could actually create a nice utility from this timer, and use it for multiple purposes and with the utility it'll stop you from repeating large chunks of code.

JavaScript Loop example:

var body = document.body;
var splittedText = ["Hello", "World", "How", "Are", "You", "Today"];

loopThroughArray(splittedText, function (arrayElement, loopTime) {
    body.innerHTML += arrayElement+ ": " + loopTime+ "<br/>";
}, 1000);

function loopThroughArray(array, callback, interval) {
    var newLoopTimer = new LoopTimer(function (time) {
        var element = array.shift();
        callback(element, time - start);
        array.push(element);
    }, interval);

    var start = newLoopTimer.start();
};

// Timer 
function LoopTimer(render, interval) {
    var timeout;
    var lastTime;

    this.start = startLoop;
    this.stop = stopLoop;

    // Start Loop
    function startLoop() {
        timeout = setTimeout(createLoop, 0);
        lastTime = Date.now();
        return lastTime;
    }
    
    // Stop Loop
    function stopLoop() {
        clearTimeout(timeout);
        return lastTime;
    }
    
    // The actual loop
    function createLoop() {
        var thisTime = Date.now();
        var loopTime = thisTime - lastTime;
        var delay = Math.max(interval - loopTime, 0);
        timeout = setTimeout(createLoop, delay);
        lastTime = thisTime + delay;
        render(thisTime);
    }
}
YaBCK
  • 2,949
  • 4
  • 32
  • 61
  • Before you made your edit, I could just run the code snippet and it would show. Could you please edit it back, because I was just working through your answer? EDIT: You just did. – LoveAndHappiness Jun 16 '15 at 11:19
  • @LoveAndHappiness yeh, sorry about that was just making an improvement. Just making it so you could use this timer in different places, without having to use the same code in different places. – YaBCK Jun 16 '15 at 11:22
  • Seems like an overkill, but I saw it working here. Might get back to you if I have trouble in the implementation, but I thank you a ton for your help. – LoveAndHappiness Jun 16 '15 at 22:57
  • 2
    @ChrisBeckett Excuse digging up an old one, but how would I stop loopThroughArray() from outside of the function? e.g. from a button? – Rhecil Codes Oct 16 '18 at 16:14
3

Ok, as It is not an exact duplicate, you need to increate the delay in the loop, also escape from the closure variable in a loop issue

loopThroughSplittedText: function (splittedText) {
    splittedText.forEach(function (text, i) {
        setTimeout(function () {
            console.log(text);
        }, i * 1000)
    })
}

var obj = {
  loopThroughSplittedText: function(splittedText) {
    splittedText.forEach(function(text, i) {
      setTimeout(function() {
        document.getElementById('x').innerHTML += text
      }, i * 1000)
    })
  }
}

obj.loopThroughSplittedText('abcde'.split(''))
<div id="x"></div>
Community
  • 1
  • 1
Arun P Johny
  • 384,651
  • 66
  • 527
  • 531
2

One problem with your code is that i is common to all the callbacks. So the first callback is told "output the entry at index i", however by the time it gets to execute the initial loop is finished so i is now at the end of the text.


One way to achieve what you're looking for is to not use a for loop, but to have a function which (1) prints a character, (2) updates the counter/position, and (3) schedules the next character if needed:

loopThroughSplitText: function (text) {
    var i = 0;
    function printEntry() {
        console.log(text[i]);
        i++; // Increment the position
        if (i < text.length) { // If there are more chars, schedule another
            setTimeout(printEntry, 1000);
        }
    }
    printEntry(); // Print the first entry/char
}
cloudfeet
  • 12,156
  • 1
  • 56
  • 57
2

solution using closure https://jsfiddle.net/x3azn/pan2oc9y/4/

function loopThroughSplittedText(splittedText) {
    var splittedText = ["Hello", "World", "How", "Are", "You", "Today"];
    for (var i = 0; i < splittedText.length; i++) {
        // for each iteration console.log a word
        // and make a pause after it
    (function(_i) {
        setTimeout(function() {
        window.document.getElementById('text').innerHTML = splittedText[_i];
        console.log(splittedText[_i]);
      }, 1000)
    }(i));

    }
}

loopThroughSplittedText()
user2167582
  • 5,986
  • 13
  • 64
  • 121
1

One more solution, with a setInterval:

var i = 0;
var intv = setInterval(function() {
    if (i >= splittedText.length) {
        clearInterval(intv);
    } else {
        console.log(splittedText[i]);
        ++i;
    }
}, 1000);
Maurice Perry
  • 32,610
  • 9
  • 70
  • 97
1

There are a couple of problems here

  1. setTimeout should take a function, not the result of calling a function
  2. setTimeout returns immediately, so all the actions in your loop will be started at roughly the same moment, and all wait 1000ms before execting (notwithstanding the comment above however, which means they're all executed at the same moment).
  3. The value of i will all be equal to splittedText.length for each iteration due to not wrapping your loop control variable in a closure.

What you need to do, is wait until the setTimeout instructions are executed before moving on to the next iteration of the loop.

For example:

var splittedText = ["Hello", "World", "How", "Are", "You", "Today"];

function loopThroughSplittedText(splittedText) {
    displayValue(splittedText,0);
}

function displayValue(arr, i){
    if(i<arr.length){
        setTimeout(function(){
            document.getElementById('text').innerHTML = arr[i];
            console.log(arr[i])
            displayValue(arr,i+1);
        },1000)
    }
}

loopThroughSplittedText(splittedText)

Live example: http://jsfiddle.net/d3whkjww/1/

Jamiec
  • 133,658
  • 13
  • 134
  • 193
1

This will also work

 function loopThroughSplittedText(splittedText) {

        for (var i=0; i < splittedText.length;i++) {
            (function(ind, text) {
                setTimeout(function(){console.log(text);}, 1000 + (1000 * ind));
        })(i, splittedText[i]);
    }

 }
Mohammad Ashfaq
  • 1,333
  • 2
  • 14
  • 38
1

Bringing out an alternative solution to the problem, which is making use of the third argument to setTimeout which is only supported in newer browsers:

(function (splittedText) {
   for (var i = 0; i < splittedText.length; i++) {
      setTimeout(
         function(val) { console.log(val); },
         i * 1000,
         splittedText[i]
      );
   }
})(["Hello", "world", "!"]);

API documentation can be seen here (note the optional params).

Jaanus Varus
  • 3,508
  • 3
  • 31
  • 49
1

Another sample:

var split = 'Lorem ipsum dolor'.split(' ');

var loop = function() {
  console.log(split[0]);
  split = split.slice(1);

  if (split.length > 0) {
    setTimeout(function() {
      loop();
    }, 1000);
  }
}

loop();
0

You can achieve by 3 ways

1. closure
2. Recursive
3. variable declaration using let

var data = ['a', 'b', 'c', 'd'];

closure:

for(i=0; i<=data.length; i++) {
  (function(x) {
    setTimeout(() => {
       console.log(x);
    }, 1000)
  })(data[i]);
 }

let variable declaration

for(const ind of data) {
 let local = ind;
 setTimeout(() => {
    console.log(local);
 }, 1000)
}