【问题标题】:Nester deferred ajax calls in loopsNester 在循环中延迟 ajax 调用
【发布时间】:2016-07-19 23:48:42
【问题描述】:

我有一个复杂的(至少对我而言)嵌套循环、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() 中的“获取所有数据”行在其他所有操作完成后以正确的顺序打印,之后应该调用 def.resolve() 并解析返回的 Promise .

但是,在 main() 中,a() 被视为立即解析,代码直接跳转到打印“全部完成”并显示页面。关于为什么它不按预期等待的任何想法?

【问题讨论】:

  • 你从来没有对def2做任何事,是吗?
  • 你的内部$.when.apply(...) 需要在它前面有一个returnreturn $.when.apply(...),这样外部的promise 就会等待内部的promise。然后,您可以摆脱不必要的反模式的外部延迟。但是,整个事情确实需要重写才能更简单、更正确地使用 Promise。

标签: javascript jquery ajax jquery-deferred


【解决方案1】:

您已经说明了一组代码并说它没有按照您的预期进行,但您还没有真正描述整体问题。所以,我实际上并不确切知道要推荐什么代码。我们在处理实际问题而不是伪代码问题时做得更好。所以,相反,我可以做的是概述一堆你的代码有问题的地方:

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

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

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

得到数据片段#0 得到数据片段#1 得到数据片段#2

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

这根本不会发生(或者如果它在现实世界中发生,那将是盲目的运气,因为没有保证顺序的代码)。现在,您可以并行运行它们并使用$.when() 跟踪它们,$.when() 将在它们全部完成时通知您,并按顺序收集所有结果。但是,该组中的每个单独的异步操作何时完成取决于机会。

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

在很多地方创建不必要的延迟 - 承诺反模式

在这段代码中,根本不需要创建 deferred。 $.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() 生成的promise,根本不需要创建deferred。这对于错误处理也更加安全。您的方法被称为反模式的原因之一是您根本不处理错误(使用此反模式时的常见错误)。但是,改进后的代码会像应有的那样将错误传播回调用者。在您的版本中,如果 $.ajax() 调用拒绝了它的承诺(由于错误),您的 deferred 永远不会得到解决,调用者也永远不会看到错误。现在,您可以编写额外的代码来处理错误,但没有理由这样做。只是回报你已经拥有的承诺。当使用返回 promise 的异步操作进行编码时,您几乎不需要创建自己的 deferred。

$.when() 仅在您有多个承诺时才需要

在你的b()函数中,这段代码不需要使用$.when()

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

当你有一个单一的承诺时,你只需直接在它上面使用.then()

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

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

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

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

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

这里有几个问题。目前尚不清楚您要做什么,因为def2 是一个没有其他用途的延迟。因此,您似乎正试图告诉某人何时完成了这组 req2 承诺,但没有人使用它。此外,它是反模式的另一个版本。 $.when() 已经返回了一个承诺。当$.when() 完成时,您不需要创建延迟来解决。你可以使用$.when()已经返回的promise。

虽然我不完全了解您在这里的意图,但您似乎应该做的是完全摆脱 def2 延迟并执行以下操作:

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

从它所在的.then() 处理程序返回这个promise 会将这个动作序列链接到父promise 并使父promise 等待这个新promise 被解决(这与所有req2承诺完成)在父承诺解决之前。这就是您如何使父级 Promise 依赖于 .then() 处理程序中的其他 Promise。您从 .then() 处理程序返回一个承诺。

而且,您的外部$.when.apply($, req) 也存在完全相同的问题。你根本不需要延迟。只需使用$.when() 已经返回的承诺即可。

放在一起

这是您的代码的清理版本,它消除了多个地方的反模式。这不会改变b(i) 调用之间的顺序。如果您关心这一点,这是一个更大的变化,我们需要看到更多真实/实际的问题才能知道最好的建议。

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) 结果,则不要在单个promise 上使用.then() 处理程序,因为它们将以任意顺序执行。相反,使用$.when().then(result1, result2, ...) 附带的结果并在那里处理它们。尽管各个 promise 以任意顺序完成,但 $.when() 会将结果收集到原始顺序中,因此如果您在 $.when() 处理程序中处理结果,那么您可以按顺序处理它们。

【讨论】:

  • 太棒了!我对原始问题进行了一些编辑,以指定这些部分不必按顺序排列,而是必须在达到 main 中的 console.log 之前全部完成。我对 b() 中的双重返回有点困惑。我理解它的方式是 $.ajax 是一个承诺,随后的 .then() 是另一个被链接的承诺(我想你已经解释了这一点),然后这第二个承诺通过嵌套返回解决?
  • @CodedMonkey - .then() 处理程序内的返回值与 b() 顶层的返回值不同。第二个返回只是指定我们希望 promise 的解析值是什么。我的b() 版本返回来自$.ajax().then() 的承诺。这是来自b() 的唯一返回值。 .then() 处理程序中的返回值指定承诺的解析值将是什么,并且不需要,除非您添加了一个 .then() 处理程序用于记录,因此您必须确认您希望解析值来自什么.then() 处理程序。
  • 那个数据对象去哪儿了呢?可以在哪里使用?
  • @CodedMonkey - 它是从b() 返回的承诺链的解析值。它会在这里b(i).then(function(data) { console.log(data)})。或者,由于您在$.when() 中收集了b(i) 承诺,它将成为$.when().then() 处理程序可用结果列表的一部分。请记住,promise 既有时间(当调用 .then() 处理程序时),也有传递给 .then() 处理程序的解析值。这是设置解析值。
  • 我想我找到了问题所在。在 a() 中,我将其作为 $.ajax().done() 而不是 .then()。我认为 .done() 是 .then(doneCallback) 的简写,但它似乎缺少该链接组件。问题已解决,已接受答案!
猜你喜欢
  • 2020-07-08
  • 1970-01-01
  • 1970-01-01
  • 2013-04-27
  • 2013-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多