我正在运行以下形式的事件循环:
var i;
var j = 10;
for (i = 0; i < j; i++) {
asynchronousProcess(callbackFunction() {
alert(i);
});
}
我正在尝试显示一系列警报,显示数字0到10。问题是,在回调函数触发之前,循环已经进行了几次迭代,���显示了更高的i
值。有什么建议可以解决这个问题吗?
我正在运行以下形式的事件循环:
var i;
var j = 10;
for (i = 0; i < j; i++) {
asynchronousProcess(callbackFunction() {
alert(i);
});
}
我正在尝试显示一系列警报,显示数字0到10。问题是,在回调函数触发之前,循环已经进行了几次迭代,���显示了更高的i
值。有什么建议可以解决这个问题吗?
for
循环会立即执行完毕,而所有的异步操作却在稍后开始。当它们完成并调用它们的回调函数时,你的循环索引变量i
将对于所有回调函数都保持在其最后一个值。for
循环在继续下一次迭代之前不会等待异步操作完成,而异步回调会在未来的某个时间被调用。因此,循环完成其迭代,然后异步操作完成时再调用回调函数。因此,循环索引已经“完成”,并且对于所有回调函数都处于其最终值。let
来定义for
循环变量,它会在每次for
循环迭代中被唯一地定义(如下面的第三个实现所示)。但是请注意,这是ES6实现中的一个晚期特性,因此你必须确保你的执行环境支持该选项。
使用.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);
}
创建或修改外部函数并将变量传递给它
如果你可以修改asynchronousProcess()
函数,那么你可以直接将值传递给它,并让asynchronousProcess()
函数将cntr传回回调函数,代码如下:
var j = 10;
for (var i = 0; i < j; i++) {
asynchronousProcess(i, function(cntr) {
console.log(cntr);
});
}
使用ES6中的let
如果您的Javascript执行环境完全支持ES6,您可以像这样在for
循环中使用let
:
const j = 10;
for (let i = 0; i < j; i++) {
asynchronousProcess(function() {
console.log(i);
});
}
for
循环的声明中使用let
,会为每次循环调用创建一个唯一的i
值(这正是你想要的)。
使用Promise和async/await进行序列化
如果您的异步函数返回一个Promise,并且您希望将异步操作序列化以依次运行而不是并行运行,并且您正在运行支持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);
}
}
这将确保仅有一个对asynchronousProcess()
的调用正在运行,并且for
循环在每个调用完成之前都不会继续进行。这与之前并行运行异步操作的方案不同,因此完全取决于您想要哪种设计。请注意:await
使用promise,因此您的函数必须返回在异步操作完成时已解决/拒绝的promise。还要注意,为了使用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);
});
asycronouseProcess()
函数,我们增加了第二个选项。 - jfriend00j
,这样做是否有问题? - Jake 1986for/of
来迭代一个数组(或者其他可迭代对象)的。 - jfriend00async 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 asycronouseProcess();
替换为 await asyncProcess();
在深入研究 async await
之前,了解 Promises
是必须的
(还要阅读有关对 async await
的支持的内容)
有没有任何建议来修复这个问题?
有几种方法可以解决。你可以使用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
的工作(在浏览器不支持它的情况下,但我会说,在这种情况下,你可以实现一个shim,它应该在上面的链接中):
for (i = 0; i < j; i++) {
asycronouseProcess(function(i) {
return function () {
alert(i)
}
}(i));
}
通常我会尽可能使用let
(例如对于Firefox插件);否则使用bind
或自定义的柯里化函数(不需要上下文对象)。
let
的功能非常好的一个。 - hazelnutasyncronouseProcess
在所有答案中都是某种占位符吗?我收到了“未定义”的错误。 - JackHasaKeyboardfunction asycronouseProcess(fn){ setTimeout(fn, 100);}
- ZER0var 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:你可以将异步代码包装在一个返回promise的函数中(比如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);
}
})();
JavaScript代码在单线程上运行,因此您原则上不能阻止等待第一次循环迭代完成后才开始下一次,否则将严重影响页面的可用性。
解决方案取决于您实际需要的内容。如果示例与您所需的内容非常接近,则@Simon的建议将i
传递给异步进程是一个不错的选择。
asynchronousProcess
函数中如何?这样可以将其传递给 callbackFunction。 - Simon Forsberg