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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 06:02:02  来源:igfitidea点击:

Asynchronous Process inside a javascript for loop

javascriptasynchronousfor-loopsynchronization

提问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 forloop 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 iwill be at its last value for all the callbacks.

for当您启动所有异步操作时,循环会立即运行直至完成。当他们在未来的某个时间完成并调用他们的回调时,您的循环索引变量的值i将是所有回调的最后一个值。

This is because the forloop 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 letto define the forloop variable and it will be uniquely defined for each iteration of the forloop (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 letin your forloop like this:

如果你有一个完全支持 ES6 的 Javascript 执行环境,你可以像这样letfor循环中使用:

const j = 10;
for (let i = 0; i < j; i++) {
    asynchronousProcess(function() {
        console.log(i);
    });
}

letdeclared in a forloop declaration like this will create a unique value of ifor 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 asyncand 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 forloop 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: awaitworks 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 awaitis 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 asycronouseProcessis returning a Promise

请记住,这仅在asycronouseProcess返回一个Promise

If asycronouseProcessis not in your control then you can make it return a Promiseby 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 Promisesbefore even looking into async awaitis 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 bindmanually (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 letwhen I can use it (e.g. for Firefox add-on); otherwise bindor 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 ito your async process is a good one.

解决方案取决于您真正需要什么。如果示例接近您所需要的,@Simon 建议传递i给您的异步进程是一个很好的建议。