JavaScript for 循环中的异步进程

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

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

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

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


答案 1

该循环立即运行到完成,同时启动所有异步操作。当它们在将来的某个时间完成并调用其回调时,循环索引变量的值将是所有回调的最后一个值。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

如果你有一个完全支持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,因此您的函数必须返回在异步操作完成时解析/拒绝的承诺。另外,请注意,为了使用 ,必须声明包含函数。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);
 });

答案 2

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

在查看异步等待之前了解承诺是必须的(另请阅读有关async await)