在 JavaScript 的 forEach 循环中处理异步操作(如接口请求)时,可能会遇到一些问题,因为 forEach 本身不会等待回调函数中的异步操作完成,就会继续同步执行下一个迭代。
问题分析
1 2 3 4 5
| const array = ['a1', 'a2', 'a3']; array.forEach(async (item) => { await fetchData(item); }); console.log('Done');
|
- 所有请求会并行触发,但
forEach 不会等待它们完成。
- 若希望顺序执行请求(一个接一个),
forEach 无法实现。
- 若希望等待所有请求完成后再处理结果,
forEach 无法直接实现。
解决方案
使用 for...of 循环(顺序执行)
for...of循环会等待异步操作完成,因为它是在循环体内部使用await,这样可以按顺序执行每个异步操作。
1 2 3 4 5 6 7
| async function processArray() { for (const item of array) { await fetchData(item); }; console.log('Done'); }; processArray();
|
使用 Promise.all(并行执行)
如果你可以同时启动所有异步操作,并且不需要顺序执行,但需要等待所有操作完成,可以使用Promise.all。
注意:Promise.all会同时启动所有异步操作,然后等待它们全部完成,如果其中任何一个失败,整个Promise.all会失败(可用 Promise.allSettled 替代)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| async function processArray() { const promises = array.map(item => fetchData(item)); const results = await Promise.all(promises); console.log('Done'); }; processArray();
async function processItems(items) { const results = await Promise.allSettled( items.map(item => processItem(item)) ); const successes = results.filter(r => r.status === 'fulfilled'); const failures = results.filter(r => r.status === 'rejected'); console.log(`成功: ${successes.length}, 失败: ${failures.length}`); };
|
使用reduce实现(顺序执行)
1 2 3 4 5 6 7
| async function processArray(array) { await array.reduce(async (previousPromise, item) => { await previousPromise; return asyncOperation(item); }, Promise.resolve()); console.log('Done'); };
|
使用 async.js 库(复杂控制)
对于更复杂的流程(如限制并发数),可以使用第三方库如 async.js
1 2 3 4 5 6 7 8
| const async = require('async');
async.eachLimit(array, 2, async (item) => { await fetchData(item); }, (err) => { if (err) console.error(err); });
|
实际应用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| async function downloadFiles(urls) { const downloadPromises = urls.map(async (url, index) => { const response = await fetch(url); const blob = await response.blob(); return { index, blob }; }); const results = await Promise.all(downloadPromises); return results.sort((a, b) => a.index - b.index); };
async function processWithRateLimit(items, limit = 100) { for (let i = 0; i < items.length; i += limit) { const batch = items.slice(i, i + limit); const batchPromises = batch.map(item => apiCall(item)); await Promise.all(batchPromises); if (i + limit < items.length) { await new Promise(resolve => setTimeout(resolve, 1000)); }; }; };
|
总结建议
- 需要顺序执行时:使用
for...of 循环
- 需要并行执行时:使用
map + Promise.all
- 需要限制并发数时:使用专门的库如
async.js、p-limit 或手动控制
- 需要处理失败时:使用
Promise.allSettled
- 避免使用:
forEach 配合异步函数