javascript for循环中的异步进程

Ben*_*rce 107 javascript synchronization asynchronous for-loop

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

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

    asynchronousProcess(callbackFunction() {
        alert(i);
    });
}
Run Code Online (Sandbox Code Playgroud)

我试图显示一系列显示数字0到10的警报.问题是,当回调函数被触发时,循环已经经历了几次迭代并且它显示了更高的值i.有关如何解决此问题的任何建议?

jfr*_*d00 189

for当所有异步操作都启动时,循环立即运行完成.当他们将来完成一些时间并调用他们的回调时,循环索引变量的值i将是所有回调的最后一个值.

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

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

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

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

someArray.forEach(function(item, i) {
    asynchronousProcess(function(item) {
        console.log(i);
    });
});
Run Code Online (Sandbox Code Playgroud)

使用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);
}
Run Code Online (Sandbox Code Playgroud)

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

如果你可以修改这个asynchronousProcess()函数,那么你可以只传递那里的值并让asynchronousProcess()cntr函数回到回调中,如下所示:

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

使用ES6 let

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

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

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

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

如果您的异步函数返回一个承诺,并希望您的序列化操作异步运行一个并行此起彼伏,而不是和你在支持现代环境中运行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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Pra*_*ena 12

async await 在这里(ES7),所以你现在可以很容易地做到这一点.

  var i;
  var j = 10;
  for (i = 0; i < j; i++) {
    await asycronouseProcess();
    alert(i);
  }
Run Code Online (Sandbox Code Playgroud)

请记住,这仅在asycronouseProcess返回时才起作用Promise

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

function asyncProcess() {
  return new Promise((resolve, reject) => {
    asycronouseProcess(()=>{
      resolve();
    })
  })
}
Run Code Online (Sandbox Code Playgroud)

然后更换该行await asycronouseProcess();通过await asyncProcess();

Promises甚至在调查之前的理解async await是必须的 (也请阅读关于支持async await)


ZER*_*ER0 10

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

一些.你可以使用bind:

for (i = 0; i < j; i++) {
    asycronouseProcess(function (i) {
        alert(i);
    }.bind(null, i));
}
Run Code Online (Sandbox Code Playgroud)

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

for (i = 0; i < j; i++) {
    let k = i;
    asycronouseProcess(function() {
        alert(k);
    });
}
Run Code Online (Sandbox Code Playgroud)

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

for (i = 0; i < j; i++) {
    asycronouseProcess(function(i) {
        return function () {
            alert(i)
        }
    }(i));
}
Run Code Online (Sandbox Code Playgroud)

我通常喜欢let什么时候可以使用它(例如对于Firefox附加组件); 否则bind或自定义currying函数(不需要上下文对象).