Skip to content Skip to sidebar Skip to footer

Nested Execution Flow Control

I've read tens of answers related to callbacks, promises and other ways to control flow, but I can't still wrap my head around this task, obviously due to my lack of competence. I

Solution 1:

You've made this question very difficult to answer because your code is so abstract and tons of relevant details are missing such as how you call test_1() and test_2(), etc..., what you're doing with the images, what obj is, etc... I know you tried to simplify things by leaving out detail, but really you just made it too abstract to know how to answer or what problem you're really trying to solve.

In JS, you can't call something and tell it to wait until something else is done. You can tell test_2() when test_1() is done. Or, you can register a sequence of functions and any time any test_n() is done, it can call the next function in the sequence. Or, you could switch to using promises and use the promise functionality for scheduling async things to run in sequence.

In the spirit of your abstract discussion, here's an abstract way of sequencing things. I've made two main changes:

  1. The rows and cells are added synchronously as you iterate the obj so that guarantees that they are added in the proper order.

  2. The separate functions are scheduled and test_2() isn't called until all functions scheduled before it have finished. This is orchestrated without calling specific function names by creating a sequenced list of functions and then having each function use seq.increment() and seq.decrement() to let the sequencer keep track of when it's done so the next function can be called.

My guess is that an actual implementation doesn't have to be this generic and a more specific solution could be simpler, but since you've kept the discussion very abstract, this is a specific way to get the rows inserted in order and an abstract way to make sure the functions are called in order and test_2() isn't called until all the images have finished from test_1().

// an object to maintain a list of functions that can be called in sequence// and to manage a completion count for each onefunctionSequencer() {
    this.list = [];
    this.cnt = 0;
}

Sequencer.prototype.add = function(/* list of function references here */) {
    this.list.push.apply(this.list, arguments);
}

Sequencer.prototype.next = function() {
    var fn = this.list.shift();
    if (fn) {
        fn(this);
    }
}

Sequencer.prototype.increment = function(n) {
    n = n || 1;
    this.cnt += n;
}

// when calling .decrement(), if the count gets to zero// then the next function in the sequence will be calledSequencer.prototype.decrement = function(n) {
    n = n || 1;
    this.cnt -= n;
    if (this.cnt <= 0) {
        this.cnt = 0;
        this.next();
    }
}

// your actual functions using the sequencer objectfunctiontest_1(seq) {
    seq.increment();
    $.each(obj, function () {
        seq.increment();
        var img = newImage();
        var row = tbl.insertRow(-1);
        var c1 = row.insertCell(0);

        img.onload = function () {
            // add row of data to table
            c1.innerHTML = "loaded";
            seq.decrement();
        };

        img.onerror = function () {
            // add row of data to table
            c1.innerHTML = "not loaded";
            seq.decrement();
        };

        img.src = this.url;
    });
    seq.decrement();
}

functiontest_2(seq) {
    seq.increment();
    $.each(obj, function () {
        seq.increment();
        var img = newImage();
        var row = tbl.insertRow(-1);
        var c1 = row.insertCell(0);

        img.onload = function () {
            // add row of data to table
            c1.innerHTML = "loaded";
            seq.decrement();
        };

        img.onerror = function () {
            // add row of data to table
            c1.innerHTML = "not loaded";
            seq.decrement();
        };

        img.src = this.url;
    });
    seq.decrement();
}

functiontest_3(seq) {
    seq.increment();
    $.each(obj, function () {
        seq.increment();
        var img = newImage();
        var row = tbl.insertRow(-1);
        var c1 = row.insertCell(0);

        img.onload = function () {
            // add row of data to table
            c1.innerHTML = "loaded";
            seq.decrement();
        };

        img.onerror = function () {
            // add row of data to table
            c1.innerHTML = "not loaded";
            seq.decrement();
        };

        img.src = this.url;
    });
    seq.decrement();
}

// code to run these in sequencevar s = newSequencer();

// add all the functions to the sequencer
s.add(test_1, test_2, test_3);

// call the first one to initiate the process
s.next();

FYI, I would personally never have code test_1, test_2 and test_3 that looks so nearly identical as I would factor it into a common piece that I could pass arguments to, but you've made this abstract so I don't know how to factor something you haven't provided any differences or specifics on.

Solution 2:

Chris, I see you have an answer but there are other ways to do this kind of thing, notably to write your test_ functions such that they return a promise of all images having loaded or not loaded, and to exploit these promises to effect an asynchronous sequence.

Ideally we would have a $.allSettled() method available to us but as jQuery doesn't have such, we need a workaround. Fortunately the workaround is very simple in this case.

For compactness, I exploit jQuery as much as possible and end up with the following :

$(function () {
    var $tbl = $('<table class="mainTbl"/>').appendTo("body");
    functiontest_1() {
        //First, use $.map() to build an array of promises from `obj`.var promises = $.map(obj, function() {
            return $.Deferred(function(dfrd) {
                var $c1 = $("<td/>").appendTo($("<tr/>").prependTo($tbl));//a concise one-liner to make the required table elements and insert them in the DOM var img = newImage();
                img.onload = dfrd.resolve;//a jQuery Deferred's resolve method is "detachable"
                img.onerror = dfrd.reject;//a jQuery Deferred's reject method is "detachable"
                img.src = this.url;
            }).then(function() {//the workaround for lack of an allSettled method is to return a resolved promise from both the done and fail callbacks of a .then() .
                $c1.html("loaded");
                return $.when(1);//a resolved promise (resolved with 1, though it's not used)
            }, function() {
                $c1.html("not loaded");
                return $.when(0);//a resolved promise (resolved with 0, though it's not used)
            });
        });
        //Now return a new promise, which will be resolved when all images are "settled" (loaded state or  error state).return $.when.apply(null, promises);
    }

    functiontest_2() {
        // identical to test_1(), or a variant as required
    }
    functiontest_3() {
        // identical to test_1(), or a variant as required
    }

    // Now, build your sequence(s) as array(s). The exact way in which you do this will depend on your application.var sequence = [test_1, test_2, test_1, test_3];//some arbitrary sequence of functions, each of which, when executed, will return a promise. // To run the seqnence, you simply exploit `array.reduce()` to build and execute a `.then()` chain, as follows :
    sequence.reduce(function(promise, fn) {
        return promise.then(function(result) {
            returnfn();
        });
    }, $.when());

});

all untested

Since the advent of .reduce(), this is rapidly becoming the de facto solution to this type of problem and maybe more like what you originally envisaged. That's the essence of it anyway. In practice, the code may well be slightly more complicated, for example :

  • if you need to pass paramters to the test_ functions
  • if you need to handle errors

Final Note: A javascript object is an orderless collection of properties. If order within your obj is important, then use an array of elements, not an object of properties.

Post a Comment for "Nested Execution Flow Control"