Nester 延迟 ajax 循环调用
Nester deferred ajax calls in loops
我有一个复杂的(至少对我来说)设置嵌套循环、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() 并且解决返回的承诺。
然而,在 main() 中,a() 被视为立即解析,代码直接跳转到打印 'All done' 并显示页面。关于为什么它没有按预期等待有什么想法吗?
您说明了一组代码并说它没有按照您的预期进行,但您还没有真正描述整个问题。所以,我实际上并不知道推荐什么代码。我们在这里处理实际问题比伪代码问题要好得多。所以,相反,我能做的是概述你的代码中的一堆错误:
期望并行异步操作的串行顺序
根据您所说的预期,您似乎缺少控制异步操作的基本逻辑。当您对一系列已经开始的承诺使用 $.when()
时,您正在 运行 并行处理一大堆异步操作。他们的完成顺序是完全不可预测的。
是的,您似乎希望能够并行 运行 一大堆 b(i)
并按顺序完成它们。情况似乎是这样,因为您说您期待这种类型的输出:
获得数据块 #0
得到数据块 #1
得到数据块 #2
其中每个语句都是通过完成某些 b(i)
操作生成的。
这根本不会发生(或者如果在现实世界中发生那将是盲目的运气,因为没有保证顺序的代码)。现在,您可以并行 运行 它们并使用 $.when()
来跟踪它们,并且 $.when()
会在它们全部完成时通知您并按顺序收集所有结果。但是当该组中的每个单独的异步操作完成时都是偶然的。
因此,如果您真的希望每个 b(i)
操作都 运行 并按顺序完成,那么您必须有目的地对它们进行排序(运行 一个,等待它完成,然后 运行 下一个,等等...)。一般来说,如果一个操作不依赖于另一个,最好并行 运行 它们并让 $.when()
跟踪它们并为您排序结果(因为您通常可以更快地获得最终结果通过 运行 将它们全部并行而不是排序)。
在很多地方创建不必要的延迟 - promse 反模式
在此代码中,根本不需要创建延迟。 $.ajax()
已经 return 是一个承诺。你可以只使用那个承诺。所以,而不是这个:
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;
});
}
请注意,您只是直接 return $.ajax()
已经生成的承诺,根本不需要创建延迟。这对于错误处理也更加可靠。您的方法被称为反模式的原因之一是您根本不处理错误(使用此反模式时的常见错误)。但是,改进后的代码会像应该的那样将错误传播回调用者。在您的版本中,如果 $.ajax()
调用拒绝了它的承诺(由于错误),您的延迟将永远不会解决,调用者也永远不会看到错误。现在,您可以编写额外的代码来处理错误,但没有理由这样做。只是 return 你已经拥有的承诺。当使用 return 承诺的异步操作进行编码时,您几乎不需要创建自己的延迟。
$.when()
只有当你有多个 promise 时才需要
在你的b()
函数中,这段代码中不需要使用$.when()
:
$.when(
$.ajax({
//params
})).then(...);
当你有一个单一的承诺时,你直接在上面使用 .then()
。
$.ajax({
//params
}).then(...);
仅当您有多个承诺并且想知道所有承诺何时完成时才使用 $.when()
。如果您只有一个承诺,只需使用它自己的 .then()
处理程序。
更多反模式 - 仅 return 来自 .then()
处理程序的承诺
在你的内部循环中,你有这个:
$.when.apply($, req2).done(function() {
console.log("Got all data pieces");
def2.resolve();
})
这里有几处错误。目前尚不清楚您要做什么,因为 def2
是一个没有其他用途的延迟。因此,看起来您正试图在这 req2
组承诺完成时告诉某人,但没有人使用它。此外,它是反模式的另一个版本。 $.when()
已经 return 是一个承诺。当 $.when()
完成时,您不需要创建延迟解析。您可以只使用 $.when()
已经 returns.
的承诺
虽然我不完全了解您的意图,但您似乎应该做的是完全摆脱 def2
延迟并执行此操作:
return $.when.apply($, req2).done(function() {
console.log("Got all data pieces");
});
从它所在的 .then()
处理程序返回此承诺会将此操作序列链接到父级承诺,并使父级承诺等待解决此新承诺(这与所有时间相关) req2
承诺完成)在父承诺解决之前。这就是您如何使父承诺依赖于 .then()
处理程序中的其他承诺。您 return 来自 .then()
处理程序的承诺。
而且,完全相同的问题也适用于您的外部 $.when.apply($, req)
。您根本不需要 deferred 。只需使用 $.when()
已经 return 的承诺。
放在一起
这是您的代码的清理版本,它在多个地方去除了反模式。这不会改变 b(i)
调用之间的顺序。如果您关心这一点,这是一个更大的变化,我们需要看到更多 real/actual 问题才能知道最好推荐什么。
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();
});
//...
}
P.S。如果你想按顺序处理同一组中的 b(i)
结果,那么不要对单个承诺使用 .then()
处理程序,因为它们将以任意顺序执行。相反,使用 $.when().then(result1, result2, ...)
附带的结果并在那里处理它们。尽管各个 promise 以任意顺序完成,但 $.when()
会将结果收集到原始顺序中,因此如果您在 $.when()
处理程序中处理结果,那么您可以按顺序处理它们。
我有一个复杂的(至少对我来说)设置嵌套循环、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() 并且解决返回的承诺。
然而,在 main() 中,a() 被视为立即解析,代码直接跳转到打印 'All done' 并显示页面。关于为什么它没有按预期等待有什么想法吗?
您说明了一组代码并说它没有按照您的预期进行,但您还没有真正描述整个问题。所以,我实际上并不知道推荐什么代码。我们在这里处理实际问题比伪代码问题要好得多。所以,相反,我能做的是概述你的代码中的一堆错误:
期望并行异步操作的串行顺序
根据您所说的预期,您似乎缺少控制异步操作的基本逻辑。当您对一系列已经开始的承诺使用 $.when()
时,您正在 运行 并行处理一大堆异步操作。他们的完成顺序是完全不可预测的。
是的,您似乎希望能够并行 运行 一大堆 b(i)
并按顺序完成它们。情况似乎是这样,因为您说您期待这种类型的输出:
获得数据块 #0 得到数据块 #1 得到数据块 #2
其中每个语句都是通过完成某些 b(i)
操作生成的。
这根本不会发生(或者如果在现实世界中发生那将是盲目的运气,因为没有保证顺序的代码)。现在,您可以并行 运行 它们并使用 $.when()
来跟踪它们,并且 $.when()
会在它们全部完成时通知您并按顺序收集所有结果。但是当该组中的每个单独的异步操作完成时都是偶然的。
因此,如果您真的希望每个 b(i)
操作都 运行 并按顺序完成,那么您必须有目的地对它们进行排序(运行 一个,等待它完成,然后 运行 下一个,等等...)。一般来说,如果一个操作不依赖于另一个,最好并行 运行 它们并让 $.when()
跟踪它们并为您排序结果(因为您通常可以更快地获得最终结果通过 运行 将它们全部并行而不是排序)。
在很多地方创建不必要的延迟 - promse 反模式
在此代码中,根本不需要创建延迟。 $.ajax()
已经 return 是一个承诺。你可以只使用那个承诺。所以,而不是这个:
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;
});
}
请注意,您只是直接 return $.ajax()
已经生成的承诺,根本不需要创建延迟。这对于错误处理也更加可靠。您的方法被称为反模式的原因之一是您根本不处理错误(使用此反模式时的常见错误)。但是,改进后的代码会像应该的那样将错误传播回调用者。在您的版本中,如果 $.ajax()
调用拒绝了它的承诺(由于错误),您的延迟将永远不会解决,调用者也永远不会看到错误。现在,您可以编写额外的代码来处理错误,但没有理由这样做。只是 return 你已经拥有的承诺。当使用 return 承诺的异步操作进行编码时,您几乎不需要创建自己的延迟。
$.when()
只有当你有多个 promise 时才需要
在你的b()
函数中,这段代码中不需要使用$.when()
:
$.when(
$.ajax({
//params
})).then(...);
当你有一个单一的承诺时,你直接在上面使用 .then()
。
$.ajax({
//params
}).then(...);
仅当您有多个承诺并且想知道所有承诺何时完成时才使用 $.when()
。如果您只有一个承诺,只需使用它自己的 .then()
处理程序。
更多反模式 - 仅 return 来自 .then()
处理程序的承诺
在你的内部循环中,你有这个:
$.when.apply($, req2).done(function() {
console.log("Got all data pieces");
def2.resolve();
})
这里有几处错误。目前尚不清楚您要做什么,因为 def2
是一个没有其他用途的延迟。因此,看起来您正试图在这 req2
组承诺完成时告诉某人,但没有人使用它。此外,它是反模式的另一个版本。 $.when()
已经 return 是一个承诺。当 $.when()
完成时,您不需要创建延迟解析。您可以只使用 $.when()
已经 returns.
虽然我不完全了解您的意图,但您似乎应该做的是完全摆脱 def2
延迟并执行此操作:
return $.when.apply($, req2).done(function() {
console.log("Got all data pieces");
});
从它所在的 .then()
处理程序返回此承诺会将此操作序列链接到父级承诺,并使父级承诺等待解决此新承诺(这与所有时间相关) req2
承诺完成)在父承诺解决之前。这就是您如何使父承诺依赖于 .then()
处理程序中的其他承诺。您 return 来自 .then()
处理程序的承诺。
而且,完全相同的问题也适用于您的外部 $.when.apply($, req)
。您根本不需要 deferred 。只需使用 $.when()
已经 return 的承诺。
放在一起
这是您的代码的清理版本,它在多个地方去除了反模式。这不会改变 b(i)
调用之间的顺序。如果您关心这一点,这是一个更大的变化,我们需要看到更多 real/actual 问题才能知道最好推荐什么。
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();
});
//...
}
P.S。如果你想按顺序处理同一组中的 b(i)
结果,那么不要对单个承诺使用 .then()
处理程序,因为它们将以任意顺序执行。相反,使用 $.when().then(result1, result2, ...)
附带的结果并在那里处理它们。尽管各个 promise 以任意顺序完成,但 $.when()
会将结果收集到原始顺序中,因此如果您在 $.when()
处理程序中处理结果,那么您可以按顺序处理它们。