javascript for 循环中的异步进程
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11488014/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Asynchronous Process inside a javascript for loop
提问by Ben Pearce
I am running an event loop of the following form:
我正在运行以下形式的事件循环:
var i;
var j = 10;
for (i = 0; i < j; i++) {
asynchronousProcess(callbackFunction() {
alert(i);
});
}
I am trying to display a series of alerts showing the numbers 0 through 10. The problem is that by the time the callback function is triggered, the loop has already gone through a few iterations and it displays a higher value of i
. Any recommendations on how to fix this?
我正在尝试显示一系列显示数字 0 到 10 的警报。问题是当回调函数被触发时,循环已经经历了几次迭代并且显示了更高的值i
。有关如何解决此问题的任何建议?
回答by jfriend00
The for
loop runs immediately to completion while all your asynchronous operations are started. When they complete some time in the future and call their callbacks, the value of your loop index variable i
will be at its last value for all the callbacks.
for
当您启动所有异步操作时,循环会立即运行直至完成。当他们在未来的某个时间完成并调用他们的回调时,您的循环索引变量的值i
将是所有回调的最后一个值。
This is because the for
loop does not wait for an asynchronous operation to complete before continuing on to the next iteration of the loop and because the async callbacks are called some time in the future. Thus, the loop completes its iterations and THEN the callbacks get called when those async operations finish. As such, the loop index is "done" and sitting at its final value for all the callbacks.
这是因为for
循环在继续循环的下一次迭代之前不会等待异步操作完成,并且因为异步回调在将来的某个时间被调用。因此,循环完成其迭代,然后在这些异步操作完成时调用回调。因此,循环索引“完成”并处于所有回调的最终值。
To work around this, you have to uniquely save the loop index separately for each callback. In Javascript, the way to do that is to capture it in a function closure. That can either be done be creating an inline function closure specifically for this purpose (first example shown below) or you can create an external function that you pass the index to and let it maintain the index uniquely for you (second example shown below).
要解决此问题,您必须为每个回调单独保存循环索引。在 Javascript 中,这样做的方法是在函数闭包中捕获它。这可以通过专门为此目的创建内联函数闭包来完成(如下所示的第一个示例),或者您可以创建一个外部函数,将索引传递给并让它为您唯一地维护索引(如下所示的第二个示例)。
As of 2016, if you have a fully up-to-spec ES6 implementation of Javascript, you can also use let
to define the for
loop variable and it will be uniquely defined for each iteration of the for
loop (third implementation below). But, note this is a late implementation feature in ES6 implementations so you have to make sure your execution environment supports that option.
截至 2016 年,如果您有一个完全符合 ES6 规范的 Javascript 实现,您还可以使用let
来定义for
循环变量,它将为循环的每次迭代唯一定义for
(下面的第三个实现)。但是,请注意,这是 ES6 实现中的一个后期实现功能,因此您必须确保您的执行环境支持该选项。
Use .forEach() to iterate since it creates its own function closure
使用 .forEach() 进行迭代,因为它创建了自己的函数闭包
someArray.forEach(function(item, i) {
asynchronousProcess(function(item) {
console.log(i);
});
});
Create Your Own Function Closure Using an IIFE
使用 IIFE 创建自己的函数闭包
var j = 10;
for (var i = 0; i < j; i++) {
(function(cntr) {
// here the value of i was passed into as the argument cntr
// and will be captured in this function closure so each
// iteration of the loop can have it's own value
asynchronousProcess(function() {
console.log(cntr);
});
})(i);
}
Create or Modify External Function and Pass it the Variable
创建或修改外部函数并将变量传递给它
If you can modify the asynchronousProcess()
function, then you could just pass the value in there and have the asynchronousProcess()
function the cntr back to the callback like this:
如果您可以修改该asynchronousProcess()
函数,那么您可以将值传递到那里,并将asynchronousProcess()
函数 cntr 返回给回调,如下所示:
var j = 10;
for (var i = 0; i < j; i++) {
asynchronousProcess(i, function(cntr) {
console.log(cntr);
});
}
Use ES6 let
使用 ES6 let
If you have a Javascript execution environment that fully supports ES6, you can use let
in your for
loop like this:
如果你有一个完全支持 ES6 的 Javascript 执行环境,你可以像这样let
在for
循环中使用:
const j = 10;
for (let i = 0; i < j; i++) {
asynchronousProcess(function() {
console.log(i);
});
}
let
declared in a for
loop declaration like this will create a unique value of i
for each invocation of the loop (which is what you want).
let
在这样的for
循环声明中声明将为循环的i
每次调用(这是您想要的)创建一个唯一的值。
Serializing with promises and async/await
使用 promise 和 async/await 进行序列化
If your async function returns a promise, and you want to serialize your async operations to run one after another instead of in parallel and you're running in a modern environment that supports async
and await
, then you have more options.
如果您的异步函数返回一个承诺,并且您希望序列化您的异步操作以一个接一个地运行而不是并行运行,并且您在支持async
和的现代环境中运行await
,那么您有更多选择。
async function someFunction() {
const j = 10;
for (let i = 0; i < j; i++) {
// wait for the promise to resolve before advancing the for loop
await asynchronousProcess();
console.log(i);
}
}
This will make sure that only one call to asynchronousProcess()
is in flight at a time and the for
loop won't even advance until each one is done. This is different than the previous schemes that all ran your asynchronous operations in parallel so it depends entirely upon which design you want. Note: await
works with a promise so your function has to return a promise that is resolved/rejected when the asynchronous operation is complete. Also, note that in order to use await
, the containing function must be declared async
.
这将确保一次只有一个调用asynchronousProcess()
正在运行,并且for
循环甚至在每个调用完成之前都不会推进。这与之前所有并行运行异步操作的方案不同,因此它完全取决于您想要的设计。注意:await
与承诺一起使用,因此您的函数必须返回一个在异步操作完成时已解决/拒绝的承诺。另请注意,为了使用await
,必须声明包含函数async
。
Run asynchronous operations in parallel and use Promise.all()
to collect results in order
并行运行异步操作并用于Promise.all()
按顺序收集结果
function someFunction() {
let promises = [];
for (let i = 0; i < 10; i++) {
promises.push(asynchonousProcessThatReturnsPromise());
}
return Promise.all(promises);
}
someFunction().then(results => {
// array of results in order here
console.log(results);
}).catch(err => {
console.log(err);
});
回答by Praveena
async await
is here
(ES7), so you can do this kind of things very easily now.
async await
在这里(ES7),所以你现在可以很容易地做这种事情。
var i;
var j = 10;
for (i = 0; i < j; i++) {
await asycronouseProcess();
alert(i);
}
Remember, this works only if asycronouseProcess
is returning a Promise
请记住,这仅在asycronouseProcess
返回一个Promise
If asycronouseProcess
is not in your control then you can make it return a Promise
by yourself like this
如果asycronouseProcess
不在您的控制范围内,那么您可以Promise
像这样自己返回一个
function asyncProcess() {
return new Promise((resolve, reject) => {
asycronouseProcess(()=>{
resolve();
})
})
}
Then replace this line await asycronouseProcess();
by await asyncProcess();
然后将这一行替换await asycronouseProcess();
为await asyncProcess();
Understanding Promises
before even looking into async await
is must(Also read about support for async await
)
Promises
甚至在调查之前async await
就必须了解(另请阅读支持async await
)
回答by ZER0
Any recommendation on how to fix this?
关于如何解决这个问题的任何建议?
Several. You can use bind:
一些。您可以使用绑定:
for (i = 0; i < j; i++) {
asycronouseProcess(function (i) {
alert(i);
}.bind(null, i));
}
Or, if your browser supports let(it will be in the next ECMAScript version, however Firefox already supports it since a while) you could have:
或者,如果您的浏览器支持let(它将在下一个 ECMAScript 版本中出现,但 Firefox 已经支持它一段时间了),您可以拥有:
for (i = 0; i < j; i++) {
let k = i;
asycronouseProcess(function() {
alert(k);
});
}
Or, you could do the job of bind
manually (in case the browser doesn't support it, but I would say you can implement a shim in that case, it should be in the link above):
或者,您可以bind
手动完成这项工作(如果浏览器不支持,但我会说在这种情况下您可以实现垫片,它应该在上面的链接中):
for (i = 0; i < j; i++) {
asycronouseProcess(function(i) {
return function () {
alert(i)
}
}(i));
}
I usually prefer let
when I can use it (e.g. for Firefox add-on); otherwise bind
or a custom curryingfunction (that doesn't need a context object).
我通常更喜欢let
什么时候可以使用它(例如用于 Firefox 附加组件);否则bind
或自定义柯里化函数(不需要上下文对象)。
回答by Black Mamba
var i = 0;
var length = 10;
function for1() {
console.log(i);
for2();
}
function for2() {
if (i == length) {
return false;
}
setTimeout(function() {
i++;
for1();
}, 500);
}
for1();
Here is a sample functional approach to what is expected here.
这是此处预期的示例功能方法。
回答by Sumer
ES2017: You can wrap the async code inside a function(say XHRPost) returning a promise( Async code inside the promise).
ES2017:您可以将异步代码包装在一个函数中(比如 XHRPost),返回一个承诺(承诺中的异步代码)。
Then call the function(XHRPost) inside the for loop but with the magical Await keyword. :)
然后在 for 循环内调用函数(XHRPost),但使用神奇的 Await 关键字。:)
let http = new XMLHttpRequest();
let url = 'http://sumersin/forum.social.json';
function XHRpost(i) {
return new Promise(function(resolve) {
let params = 'id=nobot&%3Aoperation=social%3AcreateForumPost&subject=Demo' + i + '&message=Here%20is%20the%20Demo&_charset_=UTF-8';
http.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.onreadystatechange = function() {
console.log("Done " + i + "<<<<>>>>>" + http.readyState);
if(http.readyState == 4){
console.log('SUCCESS :',i);
resolve();
}
}
http.send(params);
});
}
(async () => {
for (let i = 1; i < 5; i++) {
await XHRpost(i);
}
})();
回答by Eric J.
JavaScript code runs on a single thread, so you cannot principally block to wait for the first loop iteration to complete before beginning the next without seriously impacting page usability.
JavaScript 代码在单个线程上运行,因此您不能在不严重影响页面可用性的情况下阻止以等待第一次循环迭代完成后再开始下一次循环。
The solution depends on what you really need. If the example is close to exactly what you need, @Simon's suggestion to pass i
to your async process is a good one.
解决方案取决于您真正需要什么。如果示例接近您所需要的,@Simon 建议传递i
给您的异步进程是一个很好的建议。