Javascript how to use setTimeout on an iterative l

2019-08-23 03:12发布

问题:

I want to do something like this:

for(var i=0;i<aList.length;i++)
{
    aList[i].doSomething();
    sleep(500);
}

Of course, there's no sleep function in javascript so I tried the following:

for(var i=0;i<aList.length;i++)
{
    setTimeout(function(){
        aList[i].doSomething();
    },500);  
}

However, now it says aList[i] is not defined. Since the anonymous function is a closure, it is actually reading aList[i] from the scope of the outside function, and thus by the time the function in setTimeout is being run, i has already changed.

What is a way to accomplish this?

回答1:

A quick fix to emulate JavaScript 1.7's let is to wrap it in a function:

for(var i=0; i < aList.length; i++) {
    (function(i) {
        setTimeout(function() {
            aList[i].doSomething();
        }, 500 * i); // <-- You need to multiply by i here.
    })(i);
}

I also added a fix to a little bug in which the script will pause 500 seconds, then execute all of them. setTimeout is non-blocking.



回答2:

This is what you probably want:

for(var i=0;i<aList.length;i++)
{
    (function(i){
        // Retain `i` in this scope, for use later (after timeout)
        setTimeout(function(){
           aList[i].doSomething();
        }, 500 * i);
    })(i);
}

You want the 500 * i so that each step is 500ms later than the last, otherwise everything will happen at once after 500ms. Notice the extra wrapping function -- this essentially traps/retains the value of i.



回答3:

One of the variants:

var array = [/*elements*/],
    i,
    length = array.length,
    loop = setInterval(function() {
        array[i].doSomething();
        i += 1;
        if (i === length) {
            clearInterval(loop);
        }
    }, 500);


回答4:

Just define the function before the timeout

for(var i=0; nextFunction = aList[i];i++) {
    var waitAndDoSomething = function(func) {
        return function() { func.doSomething(); }
    };
    setTimeout(waitAndDoSomething(nextFunction),500*i);  
}

Another Possible Answer

At minitect's suggestion, I started to think about other possible solutions. A solution that I feel comes out clean is to use bind

String.prototype.doSomething = function() { alert(this); } // Just for testing
var aList = ['one', 'two', 'three']; // Just for testing

for(var i=0;obj = aList[i];i++) { 
    setTimeout(obj.doSomething.bind(obj),500*i); 
}

However, I am not sure how well this would work in the context of Razor Storm's question because I don't know what aList[i] would represent.

Does this answer the question

I also wonder if this is the intended behavior. It will not actually sleep after the execution but rather sets timing. This can be deceiving, but if we really want to execute a function and then sleep for a half a second before the next one our timing is off here. Instead I would use recursion:

String.prototype.doSomething = function() { alert(this); }
var aList = ['one', 'two', 'three'];

var doNextSomething = function(index) {
    if (!index) { index = 0 }
    if (nextObject = aList[index]) {
        nextObject.doSomething();
        setTimeout(doNextSomething, 500, index + 1);    
    }
};

doNextSomething();

Now, it will wait for 500 milliseconds after the function is executed (evident in a long running task).

I think that the answers posted were more what Razor Storm had it mind though.



回答5:

before the for loop do something like:

var _aList = aList;

Within the loop you have to setTimeout and the delay 500 * i.

So the result will be:

var _aList = aList;
for(var i=0;i<aList.length;i++)
{
    (function(obj, index) {
        setTimeout(function(){
           obj.doSomething();
        },500 * index); 
    })(aList[i], i);
}