如何依次或并行运行异步循环?

在使用循环进行异步操作之前,我想提醒大家如何编写经典的同步循环。

同步循环

很久以前,我也写过这样的循环(也许你也一样):

for (var i=0; i < array.length; i++) {
  var item = array[i];
  // do something with item
}

它很好,速度也很快,但存在许多可读性和维护问题。后来我用了它更好的版本:

array.forEach((item) => {
  // do something with item
});

JavaScript 语言的发展速度非常快。我们拥有更多的功能和新语法。其中我最喜欢的是 async/await。我现在使用它的频率越来越高。有时,我需要异步处理数组中的项目。

异步循环

如何在循环内使用 await?我们只需编写 async 函数并等待每个任务即可。

async function processArray(array) {
  array.forEach(item => {
    // define synchronous anonymous function
    // IT WILL THROW ERROR!
    await func(item);
  })
}

这段代码会出现语法错误。为什么呢?因为我们不能在同步函数中使用 await。正如你所看到的,”processArray “是同步函数。但我们用于 forEach 的匿名函数是同步函数

1.不要等待结果

如何解决之前的问题?我们也可以将匿名函数定义为异步函数:

async function processArray(array) {
  array.forEach(async (item) => {
    await func(item);
  })
  console.log('Done!');
}

forEach 不会等到所有项目都完成。它只会运行任务,然后进行下一步。为了证明这一点,让我们写一个简单的测试:

function delay() {
  return new Promise(resolve => setTimeout(resolve, 300));
}

async function delayedLog(item) {
  // notice that we can await a function
  // that returns a promise
  await delay();
  console.log(item);
}
async function processArray(array) {
  array.forEach(async (item) => {
    await delayedLog(item);
  })
  console.log('Done!');
}

processArray([1, 2, 3]);

输出结果将是:

Done!
1
2
3

如果不需要等待结果,也可以这样做。但几乎在所有情况下,这都不是一个好的逻辑。

2.依次处理队列

为了等待结果,我们应该回到老式的 “for 循环”,但这次我们可以使用带有 for…of 结构的现代版本,以获得更好的可读性:

async function processArray(array) {
  for (const item of array) {
    await delayedLog(item);
  }
  console.log('Done!');
}

这将为我们提供预期的输出结果:

1
2
3
Done!

代码将逐个串联处理每个项目。但我们可以并联运行!

3.并行处理队列

我们可以稍微修改一下代码,以便并行运行异步操作:

async function processArray(array) {
  // map array to promises
  const promises = array.map(delayedLog);
  // wait until all promises are resolved
  await Promise.all(promises);
  console.log('Done!');
}

这段代码将并行运行许多delayLog 任务。但在使用超大数组时要小心(并行执行太多任务可能会导致 CPU 或内存负荷过重)。

另外,不要把这里的 “并行 “与真正的线程和并行混为一谈。这段代码并不能保证真正的并行执行。这取决于你的项目函数(本演示中的delayLog )。网络请求、webworkers和其他一些任务可以并行执行。

仅此而已。感谢您的阅读!