如何实现在 JavaScript 循环里的 async/await
如何依次或并行运行异步循环?
在使用循环进行异步操作之前,我想提醒大家如何编写经典的同步循环。
同步循环
很久以前,我也写过这样的循环(也许你也一样):
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和其他一些任务可以并行执行。
仅此而已。感谢您的阅读!