sleeper
sleeper

Reputation: 3496

How to Use setTimeout in a for...loop

What I want is this (news ticker type functionality):

  1. Get a list of li's from a ul tag
  2. Loop through all the li's and get the text
  3. display the text in the console via firefox console.log()
  4. get the next li and repeat till all li's have been displayed

That's the goal, but setTimeout is not running as I thought it would. Only the LAST iteration ("Post Four") is showing. And that ("Post Four") is showing four times in a row.

<body>
<ul id="post_list">
 <li>Post One</li>
 <li>Post Two</li>
 <li>Post Three</li>
 <li>Post Four</li>
</ul>

<script type="text/javascript">
var ul = document.getElementById('post_list');
var li = ul.getElementsByTagName('li');

for(var x=0; x < li.length; x++){
    var li_text = li[x].childNodes[0].nodeValue;
    setTimeout(function(){showText(li_text)}, 1000);
}

function showText(text) {
    console.log(text);
}           
</script>
</body>

Upvotes: 1

Views: 534

Answers (4)

Lycha
Lycha

Reputation: 10177

As Greg mentioned the problem is with the closure only evaluating once. Nobody posted a solution for this so here is one. This uses adding a function that generates the callback function each time:

Add:

function getShowTextCallback(text) {
    return function(){showText(text)}
}

Then use it in loop like this:

for(var x=0; x < li.length; x++){
    var li_text = li[x].childNodes[0].nodeValue;
    setTimeout(getShowTextCallback(li_text), 1000);
}

Upvotes: 2

rlemon
rlemon

Reputation: 17666

How about we just move some of the code around a bit... take out the closure issue...

var ul = document.getElementById('post_list');
var li = ul.getElementsByTagName('li');

for (var x = 0, xl = li.length; x < xl; x++) {
    var li_text = li[x].innerText || li[x].textContent; // does IE support textContent??
    showText(li_text, x * 1000);
}

function showText(text, delay) {
    setTimeout(function() {
        console.log(text);
    }, delay);
}

I assume the delay you want to be sequential (hence the loop). Because setTimeout is not blocking you will need to have a callback on the function to invoke the next setTimeout or you will need to specifically increment each function call with a new delay.

Upvotes: 0

GAgnew
GAgnew

Reputation: 4083

The reason this is happening is because of closures. The for loop block has closures around it, so when you reference 'li_text' it was always equal the last value that li_text was set to. The for loop does not create a separate closure for each iteration through the loop.

Upvotes: 4

qwertymk
qwertymk

Reputation: 35274

Change this:

for(var x=0; x < li.length; x++){
    var li_text = li[x].childNodes[0].nodeValue;
    setTimeout(function(){showText(li_text)}, 1000);
}

To:

for(var x=0; x < li.length; x++) (function() {
    var li_text = li[x].childNodes[0].nodeValue;
    setTimeout(function(){showText(li_text)}, x * 1000);
})()

Upvotes: 0

Related Questions