在 javascript for 循环中的异步进程

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

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


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

我正试图显示一系列显示数字0到10的警报。问题是,在触发回调函数时,循环已经进行了几次迭代,并且显示了更高的 i值。对于如何解决这个问题有什么建议吗?

201571 次浏览

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

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

在启动所有异步操作时,for循环将立即运行到完成。当它们在将来某个时候完成并调用它们的回调函数时,循环索引变量 i的值将处于所有回调函数的最后一个值。

这是因为 for循环在继续循环的下一次迭代之前不会等待异步操作完成,而且异步回调将在将来的某个时候被调用。因此,循环完成了它的迭代,并且当那些异步操作完成时,THEN 回调被调用。因此,循环索引是“完成的”,并且对于所有回调都处于它的最终值。

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

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

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

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

创建你自己的闭包

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

创建或修改外部函数并将变量传递给它

如果您可以修改 asynchronousProcess()函数,那么您只需要将该值传递给 asynchronousProcess()函数,然后让 cntr 返回给回调函数,如下所示:

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

使用 ES6 let

如果您有一个完全支持 ES6的 Javascript 执行环境,那么您可以像下面这样在 for循环中使用 let:

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

像这样在 for循环声明中声明的 let将为循环的每次调用创建一个唯一的 i值(这是您想要的)。

使用承诺和异步/等待进行序列化

如果您的异步函数返回一个承诺,并且您希望将异步操作序列化,以便一个接一个地运行,而不是并行地运行,并且您正在一个支持 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);
}
}

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

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

有什么建议吗?

有几个。你可以使用 绑定:

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

或者,如果你的浏览器支持 (它将是下一个 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));
}

当我可以使用 let时,我通常更喜欢它(例如 Firefox 附加组件) ; 否则就选择 bind或定制的 咖喱函数(不需要上下文对象)。

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

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

请记住,这只有在 asycronouseProcess返回 Promise时才有效

如果 asycronouseProcess不在你的控制范围内,那么你可以让它像这样自己返回一个 Promise

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

然后用 await asyncProcess();代替这一行 await asycronouseProcess();

甚至在查看 async await之前就必须了解 Promises (也可以阅读对 async await的支持)

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

这里有一个示例函数方法,可以解决这里所期望的问题。

ES2017: 您可以将异步代码封装在一个返回承诺的函数(比如 XHRPost)中(承诺中的异步代码)。

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