Nester在循环

时间:2016-07-19 23:48:42

标签: javascript jquery ajax jquery-deferred

我有一个复杂的(至少对我来说)嵌套循环,ajax调用和延迟的设置。代码调用API,解析相关数据,然后使用它来进一步调用其他API。

按预期几乎。我用这个问题的答案(Using $.Deferred() with nested ajax calls in a loop)来构建它。这是我的代码:

function a() {
var def = $.Deferred();
var req = [];

for (var i = 0 /*...*/) {
    for (var j = 0 /*...*/) {
        (function(i, j) {
            req.push($.ajax({
                //params
            }).done(function(resp) {
                var def2 = $.Deferred();
                var req2 = [];

                for (var k = 0 /*...*/) {
                    for (var l = 0 /*...*/) {
                        req2.push(b(l));
                    }
                }

                $.when.apply($, req2).done(function() {
                    console.log("Got all data pieces");
                    def2.resolve();
                })
            }));
        })(i, j);
    }
}

$.when.apply($, req).done(function() {
    console.log("Got all data");
    def.resolve();
});

return def.promise();

}

function b(j) {
var def = $.Deferred();

$.when.apply(
    $.ajax({
        //params
    })
).then(function() {
    console.log("Got data piece #" + l);
    def.resolve();
});

return def.promise();

}

function main() {
//...

$.when.apply($, a()).then(function() {
    console.log("All done");
    displayPage();
})

//...

}

这是我希望在通话结束时看到的内容

(In no specific order)
Got data piece #1
Got data piece #0
Got data piece #2
Got all data pieces
Got data piece #2
Got data piece #1
Got data piece #0
Got all data pieces
Got data piece #0
Got data piece #1
Got data piece #2
Got all data pieces
Got all data            <-- These two must be last, and in this order
All done

这就是我所看到的

All done
Got data piece #0
Got data piece #1
Got data piece #2
Got all data pieces
Got data piece #0
Got data piece #1
Got data piece #2
Got all data pieces
Got data piece #0
Got data piece #1
Got data piece #2
Got all data pieces

我在调试器中逐步完成它,并且在其他所有内容完成之后,函数a()中的'Got all data'行以正确的顺序打印,之后应该调用def.resolve()并解析返回的promise

但是,在main()中,a()被视为立即解析,代码跳转到“All done”并显示页面。关于它为什么不等待它应该的任何想法?

1 个答案:

答案 0 :(得分:3)

您已经说明了一组代码,并表示它没有按照您的预期进行操作,但您还没有真正描述整体问题。所以,我实际上并不确切地知道要推荐的代码。我们在这里做的更好,有真正的问题,而不是伪代码问题。所以,相反,我能做的是勾勒出一堆代码错误的东西:

期望并行异步操作的串行顺序

根据您所说的预期,您如何控制异步操作​​的基本逻辑似乎缺失。在已经启动的一系列promise上使用$.when()时,您将并行运行一大堆异步操作。他们的完成顺序完全不可预测。

是的,您似乎希望能够并行运行一大堆b(i)并按顺序完成它们。这似乎是这种情况,因为你说你期待这种类型的输出:

获得数据#0 得到数据#1 得到数据#2

其中每个语句都是通过完成某些b(i)操作生成的。

这根本就不会发生(或者如果在现实世界中这样做会失败,因为没有代码可以保证订单)。现在,您可以并行运行它们并使用$.when()跟踪它们,$.when()会在完成所有操作后通知您,并按顺序收集所有结果。但是,当该组中的每个异步操作完成时,都有可能。

所以,如果你真的希望每个b(i)操作按顺序运行和完成,那么你必须有目的地对它们进行排序(运行一个,等待它完成,然后运行下一个,等等。 ..)。一般情况下,如果一个操作不依赖于另一个操作,最好并行运行它们并让$.when()跟踪它们并为您排序结果(因为您通常可以通过运行它们来更快地获得最终结果并行而不是对它们进行排序。)

在很多地方创造不必要的延期 - 推广反模式

在此代码中,根本不需要创建延迟。 $.ajax()已经返回了一个承诺。你可以使用这个承诺。所以,而不是:

function b(j) {
    var def = $.Deferred();

    $.when.apply(
        $.ajax({
            //params
        })
    ).then(function() {
        console.log("Got data piece #" + l);
        def.resolve();
    });
    return def.promise();
}

你可以这样做:

function b(j) {
    return $.ajax({
        //params
    }).then(function(data) {
        console.log("Got data piece #" + l);
        return data;
    });
}

请注意,您只需直接返回$.ajax()已生成的承诺,并且根本不需要创建延迟。这对于错误处理来说也更具防弹性。你的方法被称为反模式的原因之一是你根本不处理错误(使用这种反模式时常见的错误)。但是,改进的代码会将错误直接传回调用者,就像它们应该的那样。在您的版本中,如果$.ajax()调用拒绝其承诺(由于错误),您的延迟将永远不会被解决,调用者也不会看到错误。现在,您可以编写额外的代码来处理错误,但没有理由。只要回复你已经拥有的承诺。使用返回promises的异步操作进行编码时,您几乎不需要创建自己的延迟。

仅当您有多个承诺

时才需要

$.when()

b()函数中,无需在此段代码中使用$.when()

$.when(
    $.ajax({
        //params
    })).then(...);

当您有一个承诺时,您只需直接使用.then()

    $.ajax({
        //params
    }).then(...);

如果您有多个承诺,并且想知道所有承诺何时完成,请仅使用$.when()。如果您只有一个承诺,只需使用自己的.then()处理程序。

更多反模式 - 只返回.then()处理程序的承诺

在你的内循环中,你有这个:

            $.when.apply($, req2).done(function() {
                console.log("Got all data pieces");
                def2.resolve();
            })

这里有几个问题。我们不清楚你要尝试做什么,因为def2是一个延迟,没有其他用途。所以,看来你是在尝试告诉别人这个req2组的承诺,但没有人使用它。此外,它是反模式的另一个版本。 $.when()已经返回了一个承诺。 $.when()完成后,您无需创建延迟解决方案。您可以使用$.when()已经返回的承诺。

虽然我并不完全了解你的意图,但看起来你应该做的就是彻底摆脱def2延迟,并做到这一点:

            return $.when.apply($, req2).done(function() {
                console.log("Got all data pieces");
            });

从它所在的.then()处理程序返回此承诺,将这一系列操作链接到父承诺,并使父承诺等待解决此新承诺(这与所有{在父承诺解决之前完成{1}}承诺。这就是你如何使父承诺依赖于req2处理程序中的其他承诺。您从.then()处理程序返回一个承诺。

而且,对于您的外部.then(),同样的问题也是如此。你根本不需要延期。只需使用$.when.apply($, req)已经返回的承诺。

将它们放在一起

这是一个清理过的代码版本,可以摆脱多个地方的反模式。这并不会改变$.when()次呼叫的顺序。如果您关心这一点,那将是一个更大的变化,我们需要了解更多真实/实际问题,以了解最佳建议。

b(i)

P.S。如果您想按顺序处理同一组内的function a() { var req = []; for (var i = 0 /*...*/) { for (var j = 0 /*...*/) { (function(i, j) { req.push($.ajax({ //params }).then(function(resp) { var req2 = []; for (var k = 0 /*...*/) { for (var l = 0 /*...*/) { req2.push(b(l)); } } return $.when.apply($, req2).done(function() { console.log("Got all data pieces"); }); })); })(i, j); } } return $.when.apply($, req).done(function() { console.log("Got all data"); }); } function b(j) { return $.ajax({ //params }).then(function(data) { console.log("Got data piece #" + l); return data; }); } function main() { //... a().then(function() { console.log("All done"); displayPage(); }); //... } 结果,则不要在单个承诺上使用b(i)处理程序,因为这些将按任意顺序执行。相反,请使用.then()附带的结果并在那里处理它们。尽管个别承诺以任意顺序完成,但$.when().then(result1, result2, ...)会将结果收集到原始顺序中,因此如果您在$.when()处理程序中处理结果,则可以按顺序处理它们。