简体   繁体   中英

Calling a function recursively with setTimeout

I want call few function one after another recursively with setTimeout .

var flag = 0 ;
function slave1(){
    if(flag < 60) {
        var COPY_PO_LINE_DIV = document.getElementById("DOM_ELEMENT1"); // Checking if DOM has loaded or not. If yes then doing something.
        if (COPY_PO_LINE_DIV != null) {
            flag = 0;
            //doing something
        } else { 
            setTimeout(slave1,2000); //waiting for 2 seconds and checking again.
        }
    }
}

//doing similar task
function slave2(){
    if(flag < 60) {
        var COPY_PO_LINE_DIV = document.getElementById("DOM_ELEMENT2");              
        if (COPY_PO_LINE_DIV != null) {
            flag = 0;
            //doing something
        } else {
            setTimeout(slave2,2000);
        }
    }
}

function master() {
    slave1();
    console.log("Without completing slave1 function.");
    slave2();
}

Through master() function I want to call multiple functions one after another, however in current situation its calling slave2() without completing slave1() . How can I make sure that slave1() has executed completed. If DOM element is not loaded than it should execute 60 times after every 2 seconds and than it should come out from slave1() and go to next one.

I want to execute same function for 60 times if dom element is not loaded without returning the control to next function.

You need to adjust slave1 to run a callback when it is finished which will be slave2 .

function slave1(callback){
    if(flag < 60) {
        var COPY_PO_LINE_DIV = document.getElementById("DOM_ELEMENT1"); // Checking if DOM has loaded or not. If yes then doing something.
        if (COPY_PO_LINE_DIV != null) {
            flag = 0;
            //doing something
            callback();
        } else { 
            setTimeout(slave1,2000); //waiting for 2 seconds and checking again.
        }
    }
}

function slave2(){...}

function master() {
    slave1(slave2);
    console.log("Without completing slave1 function.");
}

This is your basic javascript chaining. If you have more slaves you might want to look into async.series otherwise you go into callback hell as Gabs00 has put it nicely:

slave1(function(){
    slave2(function(){
        slave3(function(){
            slave4(slave5);
        });
    });
});

If you need to pass values to callbacks then you need to use an intermediate anonymous function which in turn calls the intended callback with the arguments in question. To do that, you need define your functions so that they use the arguments:

function slave1(str, callback){...}
function slave3(i, callback){...}

slave1("some argument", function(){
    slave2("another argument", function(){
        slave3(1, function(){
            slave4(2, slave5);
        });
    });
});

Consider using promises for things like that. Here an implementation on top of jQuery, other promise libraries work similarly.

function waitForElement(elementId, maxTries, checkInterval) {
    var d = $.Deferred(), intvalID, checkFunc;

    // set up default values
    maxTries = maxTries || 60;
    checkInterval = checkInterval || 2000;

    checkFunc = function () {
        var elem = document.getElementById(elementId);
        if (maxTries-- > 0 && elem) {
            clearInterval(intvalID);
            d.resolve(elem);
        }
        if (maxTries <= 0) {
            clearInterval(intvalID);
            d.reject(elementId);
        }
    };

    // set up periodic check & do first check right-away
    intvalID = setInterval(checkFunc, checkInterval);
    checkFunc();

    return d.promise();
}

Now, if you want to test for elements one after another, you can cascade the calls like this:

function master() {
    waitForElement("DOM_ELEMENT1").done(function (elem1) {
        waitForElement("DOM_ELEMENT2").done(function (elem2) {
            alert("elem1 and elem2 exist!");
            // now do something with elem1 and elem2
        }).fail(function () {
            alert("elem1 exists, but elem2 was not found.");
        });
    }).fail(function () {
        alert("elem1 not found.");
    });
}

or you can do it in parallel and have a callback called when all of the elements exist:

function master() {
    $.when(
        waitForElement("DOM_ELEMENT1"),
        waitForElement("DOM_ELEMENT2")
    )
    .done(function (elem1, elem2) {
        alert("elem1 and elem2 exist!");
        // now do something with elem1 and elem2 
    })
    .fail(function () {
        alert("not all elements were found before the timeout");
    });
}

Your slave2 function should be passed to slave1 function as a callback and should be called in slave1 after it finishes (if ever?). Your current situation is quite common, since setTimeout() function is asynchronous, thus JS interpreter doesn't wait till the function is completed, but sets the setTimeout() result at the end of the Evet Loop and continues processing the master() method.

In order to pass arguments to functions, creating anonymous functions turns out to be an overkill. Consider using "bind" instead. So, if you've got

function slave1(str, callback){...}
function slave2(str, callback){...}
function slave3(i, callback){...}
function slave4(i, callback){...}
function slave5()

Instead of using

slave1("some argument", function(){
    slave2("another argument", function(){
        slave3(1, function(){
            slave4(2, slave5);
        });
    });
});

Consider using

slave1("some argument", 
    slave2.bind(null, "another argument",
        slave3.bind(null, 1,
            slave4.bind(null, 2, slave5)
        )
    )
);

Much easier, more efficient in terms of memory and CPU utilization. Now, how to do this with setTimeout:

slave1("some argument",
    setTimeout.bind(null, slave2.bind(null, "another argument",
        setTimeout.bind(null, slave3.bind(null, 1,
            setTimeout.bind(null, slave4.bind(null, 2,
                setTimeout.bind(null, slave5, 0)
            ),0)
        ),0)
    ),0)
);

I explained the problem in more detail at http://morethanslightly.com/index.php/2014/09/executables-the-standard-solution-aka-mind-the-bind/

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