Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

setTimeout on Asynchronous function

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);
    }
});
like image 491
Nyxynyx Avatar asked Nov 26 '11 14:11

Nyxynyx


3 Answers

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);
    }
});
like image 80
Rob W Avatar answered Nov 08 '22 22:11

Rob W


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');
    }
});
like image 41
Thomas Avatar answered Nov 08 '22 20:11

Thomas


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

like image 30
RightSaidFred Avatar answered Nov 08 '22 22:11

RightSaidFred