为什么我的for循环直接跳到结尾?
为什么我的for循环直接跳到最后?
这个问题的原因是,由于JavaScript的异步特性,for循环在执行异步操作时会出现问题。在上面的代码中,asycronouseProcess函数是一个异步函数,当循环执行到asycronouseProcess时,JavaScript不会等待异步操作完成,而是继续执行下一次循环。因此,当异步操作完成时,循环已经执行完了,导致alert(i)的结果都是最后一次循环的值。
解决这个问题的方法有几种。第一种方法是使用bind函数。在循环中,使用bind函数将i作为参数传递给异步函数,并绑定上下文为null。这样可以确保每次异步函数执行时,都能获取到正确的i的值。
第二种方法是使用let关键字。如果浏览器支持let关键字,可以将循环变量i声明为块级作用域的变量,这样每次循环都会创建一个新的变量。这样,在异步函数执行时,每个函数都能获取到自己对应的i的值。
第三种方法是手动实现bind函数。在循环中,可以创建一个立即执行函数,将i作为参数传递给该函数,并返回一个新的函数。这样可以确保每次异步函数执行时,都能获取到正确的i的值。
在上面的例子中,作者提到了他更喜欢使用let关键字或者bind函数来解决这个问题。如果浏览器支持let关键字,可以优先选择使用let关键字。否则,可以使用bind函数或者自定义的柯里化函数来解决这个问题。
最后,关于asyncronouseProcess函数的问题,它是原问题中的占位符函数,用来表示任意的异步函数。你可以将其替换为任何你想测试的异步函数,例如setTimeout函数等。
解决这个问题的方法有三种:使用bind函数、使用let关键字或者手动实现bind函数。根据浏览器的支持情况和个人喜好,选择适合自己的方法即可。
为什么我的for循环直接到达末尾?
在ES7中引入了async await
,所以现在你可以很容易地做到这种事情。
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
的支持)。
每次循环都会等待吗?
是的,它会等待(如果asycronouseProcess()
返回一个promise)。
从异步函数返回一个promise解决了我的问题。谢谢!
为什么我的for循环直接跳到最后?
原因:
for循环在所有异步操作开始后立即完成。当它们在未来某个时刻完成并调用它们的回调函数时,循环索引变量i的值对于所有回调函数来说都将是最后一个值。这是因为for循环在继续下一次迭代之前不等待异步操作完成,并且异步回调在未来某个时刻被调用。因此,循环完成其迭代,然后在异步操作完成时调用回调函数。因此,循环索引对于所有回调来说都已经“完成”并且保持在最后一个值。
解决方法:
要解决这个问题,必须为每个回调单独保存循环索引。在Javascript中,可以通过在函数闭包中捕获它来实现。可以通过创建一个专门用于此目的的内联函数闭包(如下面的第一个示例)或者创建一个外部函数,将索引传递给它,并让它为您单独维护索引(如下面的第二个示例)。
使用.forEach()进行迭代,因为它会创建自己的函数闭包
someArray.forEach(function(item, i) { asynchronousProcess(function(item) { console.log(i); }); });
使用立即执行函数创建自己的函数闭包
var j = 10; for (var i = 0; i < j; i++) { (function(cntr) { // 这里将i的值作为参数cntr传递进来 // 并且将在该函数闭包中捕获,以便每次循环迭代都有自己的值 asynchronousProcess(function() { console.log(cntr); }); })(i); }
创建或修改外部函数并将变量传递给它
如果可以修改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来定义循环变量,这样每次循环迭代都会创建一个唯一的i值(下面是第三个示例)。
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++) { // 等待promise解决后再推进for循环 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(asynchronousProcessThatReturnsPromise()); } return Promise.all(promises); } someFunction().then(results => { // 按顺序在这里获取结果数组 console.log(results); }).catch(err => { console.log(err); });
如果可以修改asynchronousProcess()函数,还有第二个选项。
有一个问题,如果在异步函数内部增加计数器然后检查它是否等于j,是否会出错?
阅读这篇文章以获取更多解释- async/await
- 我明白你的意思,但是问题并没有显示一个数组或其他数据集合,所以似乎这个问题实际上并不涉及使用for/of迭代数组(或其他可迭代对象)。
这是我读过的最清晰的关于JS异步行为的例子。你有个博客吗?