9

I have a asynchronous function that I want to have a 5000ms delay before being fired. I am trying to use setTimeout() to achieve this. This async function occurs in a loop that runs several times, with the async function being passed different data each time, thus setInterval() cannot be used here.

Problem: The async function gets triggered instantly without delay (console prints 5 Done messages instantly` and loops without any delay. What happened, and how can I solve it?

Javascript Code

someFunction(listings, function() {
    for (var i in listings ) {
        var listing = listings[i];
        setTimeout(asyncFunction(listing, function(data) {
            console.log('Done');
        }), 5000);
    }
});
Nyxynyx
  • 61,411
  • 155
  • 482
  • 830

4 Answers4

13

You have to wrap the function in another function. Currently, you're invoking the function, and passing the return value as an argument to setTimeout. The code below will (correctly) pass a function to setTimeout. After 5 seconds, the function executes.

I had to add two functions to achieve the desired behaviour, because of scoping issues. After 5 seconds, the loop has already finished, and the listing variable would be equal to the last element in listings.

someFunction(listings, function() {
    var counter = 0;  // Define counter for 5 second-delays between each call
    for (var i in listings ) {
        var listing = listings[i];
        (function(listing){ //Closure function
            setTimeout(function(){ //setTimeout function
                // Because of the closure, `listing` is unique
                asyncFunction(listing, function(a, b) {
                    console.log('Done');
                });
            }, 5000 * ++counter); //Increase counter for each loop
        })(listing);
    }
});
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • this will still trigger n timeouts almost parallel. – jAndy Nov 26 '11 at 14:23
  • Thanks for the reply! There is an initial 5000ms delay, but the subsequent calls to the asyncFunction occurs instantly! Can the subsequent calls to the functions have delays such that each call is 5000ms apart? – Nyxynyx Nov 26 '11 at 14:24
  • it also has the classic loop/scope problem (the "listing" variable) – Pointy Nov 26 '11 at 14:24
  • I noticed all the async functions get the same `listing` variable as pointy pointed out – Nyxynyx Nov 26 '11 at 14:26
  • Updated answer to add a 5 seconds delay between each call. @Pointy That scope problem has already been adressed in my ninja-edit at the first revision. – Rob W Nov 26 '11 at 14:26
  • @jAndy Now, the functions are triggered with a 5 second delay in between. – Rob W Nov 26 '11 at 14:32
  • @RobW Awesome, solved several problems here! I don't understand how the closure function works: `function(listings) {...}(listings)` Why is there an additional `(listings)` appended to the end of the function? Have never seen this before... – Nyxynyx Nov 26 '11 at 14:49
  • 1
    @Nyxynyx Normally, you invoke a function by `func_name(parameter)`. Instead of defining a named function, you can also create and invoke an anonymous function: `(function(argument){...})(parameter)`. By passing `listing` to the anonymous function, the `listing` variable inside the anonymous function will not change when the `listing` variable in the loop's body is updated again. – Rob W Nov 26 '11 at 14:52
7

If you are using ECMAScript6 you can use Promise.

So create a delay function that wrap the call to the setTimeout into a Promise:

function delay(ms) {
    return new Promise(function (resolve) { return setTimeout(resolve, ms); });
};

And you can use it like that :

someFunction(listings, function() {
    for (var i in listings ) {
        var listing = listings[i];
        delay(5000).then(() => {
            return asyncFunction(listing);
        }).then(() => {
            console.log('Done');
        });
    }
});

If you are using ECMAScript 2017 you can use aync/await.

Async functions return promise so you don't have to change the code of the delay function.

async someFunction(listings, function() {
    for (var i in listings ) {
        var listing = listings[i];
        await delay(5000);
        await asyncFunction(listing);
        console.log('Done');
    }
});
Thomas
  • 24,234
  • 6
  • 81
  • 125
1

Not knowing what your asyncFunction does, it would seem that it could simply return the function you passed it.

someFunction(listings, function() {
    for (var i = 0; i < listings.length; ++i ) {
        setTimeout(asyncFunction(listings[i], function(data) {
            console.log('Done');
        }), 5000 * i);
    }
});

function asyncFunction( lstng, func ) {
    return func;
}

Though I'd expect that you need to wrap up some additional logic.

function asyncFunction( lstng, func ) { 
    return function() {
        // do some stuff with the listing

        //   then invoke the func
        func();
    }
}

Now your asyncFunction wraps whatever is needed in a new function that is returned to the setTimeout. The new function also invokes the callback you passed.


JSFIDDLE DEMO

RightSaidFred
  • 11,209
  • 35
  • 35
  • Thanks, reduced a line of code by using `i` instead of `counter`. asyncFunction uses 2 callback values inside `data` and sends them to another server. – Nyxynyx Nov 26 '11 at 15:13
  • @Nyxynyx: Not entirely sure what you mean, but you can pass whatever you need to the callback func. My main point was that you already have a named function, so there's no need to add two levels of scope inline. You just need to modify `asyncFunction` so that it *returns* a function to be invoked by the `setTimeout`. This is a cleaner approach. – RightSaidFred Nov 26 '11 at 15:21
-1

it's the difference.the key point is what is asyncFunction do? can you paste it out?

var foo=function(){
    alert("BAR");
    return function(){
        alert("I AM!");
    };
}
setTimeout(foo(),4000);
setTimeout(foo,5000);
island205
  • 1,730
  • 17
  • 28
  • 4
    The first "setTimeout()" is incorrect, as it will immediately invoke "foo()" and pass its *result* to "setTimeout()". The second, passing a string, is generally considered deprecated. – Pointy Nov 26 '11 at 14:26
  • yes, passing string will `eval` it and then run it on global scope. Very Bad Idea – Rifat Nov 26 '11 at 15:18