javascript for 循环中的异步进程 [duplicate]

Asynchronous Process inside a javascript for loop [duplicate]

提问人:Ben Pearce 提问时间:7/15/2012 最后编辑:pushkinBen Pearce 更新时间:1/5/2020 访问量:227363

问:

这个问题在这里已经有答案了:
5年前关闭。

社群在 12 个月前审查了是否重新打开这个问题,并关闭了这个问题:

重复这个问题已经得到回答,不是唯一的,也没有与另一个问题区分开来。

我正在运行以下形式的事件循环:

var i;
var j = 10;
for (i = 0; i < j; i++) {

    asynchronousProcess(callbackFunction() {
        alert(i);
    });
}

我正在尝试显示一系列显示数字 0 到 10 的警报。问题在于,当触发回调函数时,循环已经经历了几次迭代,并且它显示了更高的值 。关于如何解决这个问题的任何建议?i

JavaScript 异步 for 循环 同步

评论

0赞 Simon Forsberg 7/15/2012
将 i 参数添加到函数中怎么样?可以将其传递给 callbackFunctionasynchronousProcess
0赞 Drkawashima 12/2/2022
传入 callbackFunction 以确保在调用函数时处理该变量的值,而不是在函数返回后处理该变量的值。i

答:

2赞 Eric J. 7/15/2012 #1

JavaScript 代码在单个线程上运行,因此您不能在不严重影响页面可用性的情况下,主要阻止等待第一次循环迭代完成,然后再开始下一个循环迭代。

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

323赞 jfriend00 7/15/2012 #2

循环将立即运行直至完成,同时启动所有异步操作。当它们在将来的某个时间完成并调用其回调时,循环索引变量的值将处于所有回调的最后一个值。fori

这是因为循环不会等待异步操作完成,然后继续循环的下一次迭代,并且异步回调会在将来的某个时间调用。因此,循环完成其迭代,然后在这些异步操作完成时调用回调。因此,循环索引是“完成”的,并且位于所有回调的最终值处。for

要解决此问题,您必须为每个回调单独保存循环索引。在 Javascript 中,执行此操作的方法是在函数闭包中捕获它。这可以通过专门为此目的创建一个内联函数闭包来完成(下面显示的第一个示例),也可以创建一个将索引传递给的外部函数,并让它为您唯一地维护索引(下面显示的第二个示例)。

截至 2016 年,如果您有一个完全符合规范的 Javascript ES6 实现,您还可以用于定义循环变量,它将为循环的每次迭代唯一定义(下面的第三个实现)。但是,请注意,这是 ES6 实现中的后期实现功能,因此您必须确保执行环境支持该选项。letforfor

使用 .forEach() 进行迭代,因为它创建了自己的函数闭包

someArray.forEach(function(item, i) {
    asynchronousProcess(function(item) {
        console.log(i);
    });
});

使用 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);
}

创建或修改外部函数并向其传递变量

如果你可以修改该函数,那么你可以将值传递到那里,并将函数 cntr 返回回调,如下所示:asynchronousProcess()asynchronousProcess()

var j = 10;
for (var i = 0; i < j; i++) {
    asynchronousProcess(i, function(cntr) {
        console.log(cntr);
    });
}

使用 ES6 let

如果你有一个完全支持 ES6 的 Javascript 执行环境,你可以在循环中使用,如下所示:letfor

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

let在这样的循环声明中声明将为每个循环调用创建一个唯一的值(这是你想要的)。fori

使用 promise 和 async/await 进行序列化

如果您的异步函数返回一个 promise,并且您希望序列化您的异步操作以一个接一个地运行,而不是并行运行,并且您在支持 和 的新式环境中运行,那么您有更多选择。asyncawait

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);
    }
}

这将确保一次只有一个调用在运行中,并且在每个调用完成之前,循环甚至不会前进。这与以前的方案不同,以前的方案都并行运行异步操作,因此这完全取决于所需的设计。注意:使用 promise,因此您的函数必须返回一个 promise,该 promise 在异步操作完成时被解析/拒绝。另外,请注意,为了使用 ,必须声明包含函数。asynchronousProcess()forawaitawaitasync

并行运行异步操作,并使用 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);
 });

评论

1赞 jfriend00 7/15/2012
如果可以修改函数,则添加了第二个选项。asycronouseProcess()
1赞 Jake 1986 6/30/2017
增加计数器然后检查它是否等于异步函数内部是错误的吗?j
1赞 Srinivasan K K 4/4/2019
值得一读的解释 - async/await
1赞 jfriend00 4/9/2020
@SeanMC - 我遵循你说的,但这个问题实际上并没有显示任何内容的数组,所以这个问题似乎实际上不是关于迭代数组(或一些可迭代的)是关于什么的。for/of
1赞 Demian Sims 2/14/2021
这是我读过的 JS 中异步行为最明显的例子之一。你有博客吗?
11赞 ZER0 7/15/2012 #3

关于如何解决这个问题的任何建议?

几个。您可以使用 bind

for (i = 0; i < j; i++) {
    asycronouseProcess(function (i) {
        alert(i);
    }.bind(null, i));
}

或者,如果你的浏览器支持 let(它将在下一个 ECMAScript 版本中,但 Firefox 已经支持它了),你可以有:

for (i = 0; i < j; i++) {
    let k = i;
    asycronouseProcess(function() {
        alert(k);
    });
}

或者,您可以手动完成这项工作(以防浏览器不支持它,但我想说您可以在这种情况下实现垫片,它应该在上面的链接中):bind

for (i = 0; i < j; i++) {
    asycronouseProcess(function(i) {
        return function () {
            alert(i)
        }
    }(i));
}

我通常更喜欢在我可以使用它的时候(例如,用于Firefox附加组件);否则或自定义 currying 函数(不需要上下文对象)。letbind

评论

0赞 hazelnut 1/14/2016
ECMAScript 示例是一个很好的示例,可以演示可以做什么。let
1赞 JackHasaKeyboard 11/20/2017
在所有答案中都是某种占位符吗?我得到“未定义”。asyncronouseProcess
1赞 ZER0 11/22/2017
这是原始问题的一部分,所以是的,如果它给你“未定义”是正常的。如果您想检查原始问题以及建议的解决方案的工作原理,您可以将其替换为任何异步函数。例如:asyncronouseProcessfunction asycronouseProcess(fn){ setTimeout(fn, 100);}
32赞 Praveena 6/12/2017 #4

async await在这里 (ES7),所以你现在可以很容易地做这种事情。

  var i;
  var j = 10;
  for (i = 0; i < j; i++) {
    await asycronouseProcess();
    alert(i);
  }

请记住,仅当 asycronouseProcess 返回 Promise 时,这才有效

如果不受您的控制,那么您可以像这样自己让它返回asycronouseProcessPromise

function asyncProcess() {
  return new Promise((resolve, reject) => {
    asycronouseProcess(()=>{
      resolve();
    })
  })
}

然后将这一行替换为await asycronouseProcess();await asyncProcess();

在研究异步 await 之前了解 Promise 是必须的(另请阅读async await)

评论

0赞 Shamoon 5/6/2020
循环的每次迭代都会等待吗?
0赞 Gerard Setho 7/24/2020
@Shamoon是的。它将等待(如果 asycronouseProcess() 返回一个 promise)
1赞 ozzyzig 3/1/2023
@Praveena。从异步函数返回 promise 解决了我的问题。谢谢
4赞 Black Mamba 8/1/2017 #5

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();

下面是此处预期的示例功能方法。

4赞 Sumer 8/18/2018 #6

ES2017:你可以将异步代码包装在一个函数中(比如 XHRPost),返回一个 promise( promise 中的异步代码)。

然后在 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);
       }
})();