嵌套执行流程控制

时间:2014-05-29 04:13:57

标签: javascript jquery promise

我已经阅读了数十个与回调,承诺和其他控制流程相关的答案,但我仍然不能完成这项任务,显然是因为我缺乏能力。

我有一个嵌套问题:

  1. test_1()(以及其他函数)中,我想确保根据元素在对象中的顺序将行添加到表中;
  2. 我想在test_1完全完成后执行test_2或test_3(或两者之后)。实际上,正确的序列只能在运行时知道(会有一个带有可能序列的开关,如1,2,3或1,3,2或1,2,1,3或1,3,3,2,等...)
  3. 代码:

    $(function () {
    
            // create table
            tbl = document.createElement('table');
            tbl.className = "mainTbl";
            $("body").append(tbl);    
    
        });
    
        function test_1() {
                $.each(obj, function () {
                    var img = new Image();
    
                    img.onload = function () {
                        // add row of data to table
                        var row = tbl.insertRow(-1);
                        var c1 = row.insertCell(0);
                        c1.innerHTML = "loaded";
                    };
    
                    img.onerror = function () {
                        // add row of data to table
                        var row = tbl.insertRow(-1);
                        var c1 = row.insertCell(0);
                        c1.innerHTML = "not loaded";
                    };
    
                    img.src = this.url;
                });
        }
    
    function test_2() {
                $.each(obj, function () {
                    var img = new Image();
    
                    img.onload = function () {
                        // add row of data to table
                        var row = tbl.insertRow(-1);
                        var c1 = row.insertCell(0);
                        c1.innerHTML = "loaded";
                    };
    
                    img.onerror = function () {
                        // add row of data to table
                        var row = tbl.insertRow(-1);
                        var c1 = row.insertCell(0);
                        c1.innerHTML = "not loaded";
                    };
    
                    img.src = this.url;
                });
        }
    function test_3() {
                $.each(obj, function () {
                    var img = new Image();
    
                    img.onload = function () {
                        // add row of data to table
                        var row = tbl.insertRow(-1);
                        var c1 = row.insertCell(0);
                        c1.innerHTML = "loaded";
                    };
    
                    img.onerror = function () {
                        // add row of data to table
                        var row = tbl.insertRow(-1);
                        var c1 = row.insertCell(0);
                        c1.innerHTML = "not loaded";
                    };
    
                    img.src = this.url;
                });
        }
    

    我知道顺序调用函数不起作用,因为它们不等待彼此......我认为承诺是他们的方式去但我找不到合适的组合,文档也是如此我的技能很复杂。

    构造代码的最佳方法是什么,以便以正确的顺序执行?

2 个答案:

答案 0 :(得分:1)

您已经很难回答这个问题,因为您的代码非常抽象,缺少大量相关详细信息,例如您如何调用test_1()test_2()等等,你对图像做了什么,obj是什么等等......我知道你试图通过省略细节来简化事情,但实际上你只是让它过于抽象而不知道如何回答或者是什么你真正试图解决的问题。

在JS中,您无法调用某些内容并告诉它等待其他内容完成。完成test_2()后,您可以告诉test_1()。或者,您可以注册一系列函数,只要完成任何test_n(),它就可以调用序列中的下一个函数。或者,您可以切换到使用promises并使用promise功能来安排异步事件按顺序运行。

本着抽象讨论的精神,这是一种抽象的事物排序方式。我做了两个主要的改变:

  1. 在迭代obj时会同步添加行和单元格,以确保以正确的顺序添加它们。

  2. 安排了单独的功能,并且在完成所有功能调度之前,不会调用test_2()。通过创建一个有序的函数列表,然后让每个函数使用seq.increment()seq.decrement()让序列发生器跟踪它何时完成,以便下一个函数,可以在不调用特定函数名的情况下进行编排。可以被称为。

  3. 我的猜测是,实际的实现并不一定是这种通用的,更具体的解决方案可能更简单,但由于您已经将讨论保持为抽象,这是一种特定的方法来获得按顺序插入的行和一种抽象的方法,以确保按顺序调用函数,并且test_2()不会被调用,直到所有图像都从test_1()完成。

    // an object to maintain a list of functions that can be called in sequence
    // and to manage a completion count for each one
    function Sequencer() {
        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 called
    Sequencer.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 object
    function test_1(seq) {
        seq.increment();
        $.each(obj, function () {
            seq.increment();
            var img = new Image();
            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();
    }
    
    function test_2(seq) {
        seq.increment();
        $.each(obj, function () {
            seq.increment();
            var img = new Image();
            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();
    }
    
    function test_3(seq) {
        seq.increment();
        $.each(obj, function () {
            seq.increment();
            var img = new Image();
            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 sequence
    var s = new Sequencer();
    
    // 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();
    

    仅供参考,我个人永远不会有代码test_1,test_2和test_3看起来几乎完全相同,因为我会将它分解为一个我可以传递参数的共同部分,但是你已经把这个抽象了,所以我不会# 39;不知道如何分析你没有提供任何差异或细节的东西。

答案 1 :(得分:1)

克里斯,我看到你有答案,但还有其他方法可以做这种事情,特别是编写你的test_函数,以便他们返回所有已加载或未加载的图像的承诺,并且利用这些承诺来实现异步序列。

理想情况下,我们可以使用$.allSettled()方法,但由于jQuery没有这种方法,我们需要一种解决方法。幸运的是,在这种情况下,解决方法非常简单。

为了实现紧凑性,我尽可能地利用jQuery并最终得到以下结果:

$(function () {
    var $tbl = $('<table class="mainTbl"/>').appendTo("body");
    function test_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 = new Image();
                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);
    }

    function test_2() {
        // identical to test_1(), or a variant as required
    }
    function test_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) {
            return fn();
        });
    }, $.when());

});

所有未经测试的

.reduce()出现以来,这很快成为此类问题的事实上的解决方案,可能更像您最初设想的那样。无论如何,这就是它的本质。在实践中,代码可能稍微复杂一些,例如:

  • 如果您需要将参数传递给test_ functions
  • 如果您需要处理错误

最终注释:javascript对象是无序的属性集合。如果 中的obj很重要,那么请使用元素数组,而不是属性对象。