简体   繁体   中英

setTimeout Function with Dynamic Duration

I have a setTimeout function that iterates over values:

 var content = ['one', 'two', 'three', 'four', 'five'] var duration = ['10000', '10000', '20000', '10000', '30000'] for (i = 0; i < content.length; i++) { setTimeout(function () { alert("hi") }, duration[i]) }

However, the duration is not dynamic. I've also attempted parseInt(duration[i]) but that doesn't work either. What am I doing wrong here?

-------------Clarification-----------------:

As I understand it, the setTimeout() method "calls a function or evaluates an expression after a specified number of milliseconds" 1 . As the abstract code above quite clearly suggests (though it may take more than a brief glance to determine), for each item in the content array, there are a set of operations that are performed. Each operation is associated with a duration of time, in milliseconds, that the current operation should wait until the next operation begins.

In the example above, upon initiating the function, the initial wait duration, 10000 milliseconds, which is the first item in the duration array, seems to called as expected. However, subsequent operations do not wait the duration specified by their respective value in the duration array.

As another poster mentioned, you probably want each timeout to start from the end of the last one. You could use the suggested "recursive" approach, but promises would result in more readable code. First, we'll write a wait routine which returns a promise which resolves in some number of milliseconds:

function wait(n) {
    return new Promise(function(resolve) { setTimeout( resolve, n); });
}

Now we can rewrite your loop as follows:

var promise = Promise.resolve();

for (i = 0; i < content.length; i++) {
    promise = promise . then(function() { return wait(duration[i]; });
}

We need the Promise.resolve() to kick things off with a promise which is already resolved. By the way, as a bonus, at the end of this loop you have a promise for the entire chain of timeouts having completed, which you can use to do something next.

It turns out that this is exactly the same as

duration.reduce(
    function(promise, ms) {
        return promise . then(function() { return wait(ms); });
    }, Promise.resolve()
);

If you don't know reduce , it's well worth learning.

But imagine how nice it would be to skip all this, and simply have something which instead of setting up a timeout, waits right there . It turns out you can in fact do this, using async functions:

async function wait_series(duration) {
^^^^^
    for (i = 0; i < content.length; i++) {
        await wait(duration[i]);
        ^^^^^
    }
}

Async functions will require using an appropriate transpiler, such as babel, with the appropriate flags.

If you don't have or want to use Babel, but do have access to generators , you could use a library like co to turn a generator ( function* ) into an asynchronous function, with yield where you want to wait:

function wait_series(duration) {
    co(function *() {
    ^^
        for (i = 0; i < content.length; i++) {
            yield wait(duration[i]);
            ^^^^^
        }
    });
}

The advantage of using async functions, or their co -based equivalent, is that you can go back to writing regular old for loops like you are used to. They essentially allow you to write asynchronous code with a synchronous mindset. You no longer have to think about promises (although co does its magic under the hood using them).

You may say that this seems like a lot of stuff to learn just to write a sequential series of timeouts. But actually, promises and async functions are easier in the end than writing timeouts which call functions which then set up other timeouts.

Recursive solution

If you prefer the recursive solution, here is a slightly streamlined version of it, that also actually works (by invoking loop the first time via the () at the end):

function wait_series(duration) {
    var i = duration.length;
    (function loop() {
        if (i--) setTimeout(loop, duration[i]);
    })();
      ^^
}

The problem with your code is that the for-loop iterates without waiting for the setTimeout to complete.
I recommend using a bit of recursion like this

 var content = ['one','two','three','four','five']; var duration = ['10000','10000','20000','10000','30000']; var i = 0; var loop = function(){ setTimeout(function(){ if(++i < content.length); loop(); },parseInt(duration[i])); }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM