JavaScript循环中异步处理之for、forEach、for...of、for...in的区别详解
作者:Fantastic_sj
在JavaScript中,遍历数组和对象是前端开发中常见的任务之一,而为了完成这项任务,开发者们通常会使用不同类型的循环,这篇文章主要介绍了JavaScript循环异步处理之for、forEach、for...of、for...in区别的相关资料,需要的朋友可以参考下
一、同步循环与异步循环的核心区别
同步循环(阻塞执行)
// 同步执行:每个循环完全执行完才进入下一个
for (let i = 0; i < 3; i++) {
console.log(`同步 ${i} 开始`);
const result = syncTask(i); // 同步任务
console.log(`同步 ${i} 结束: ${result}`);
}
// 输出顺序:同步0开始 → 同步0结束 → 同步1开始 → 同步1结束 → 同步2开始 → 同步2结束异步循环(非阻塞执行)
// 异步执行:不等异步任务完成就继续下一个循环
for (let i = 0; i < 3; i++) {
console.log(`异步 ${i} 开始`);
asyncTask(i).then(result => {
console.log(`异步 ${i} 结束: ${result}`);
});
}
// 输出顺序:异步0开始 → 异步1开始 → 异步2开始 → (异步任务结果随机出现)二、不同循环方式的异步行为对比
1. 普通 for 循环
// 基本语法
for (let i = 0; i < 3; i++) {
// 循环体
}异步特性分析:
console.log('开始');
for (let i = 0; i < 3; i++) {
console.log(`循环 ${i} 开始`);
// 情况1:包含异步操作
setTimeout(() => {
console.log(`setTimeout ${i}`);
}, Math.random() * 100);
// 情况2:包含Promise
Promise.resolve()
.then(() => console.log(`Promise ${i}`));
}
console.log('结束');
// 输出顺序:
// 开始
// 循环0开始
// 循环1开始
// 循环2开始
// 结束
// Promise 0
// Promise 1
// Promise 2
// setTimeout 0(时间随机)
// setTimeout 1
// setTimeout 22. forEach 循环
// 基本语法
[0, 1, 2].forEach((item, index) => {
// 循环体
});异步特性分析:
console.log('开始');
[0, 1, 2].forEach(async (item, index) => {
console.log(`forEach ${index} 开始`);
// 异步操作无法阻塞forEach
await new Promise(resolve =>
setTimeout(resolve, Math.random() * 100)
);
console.log(`forEach ${index} 结束`);
});
console.log('结束');
// 输出顺序:
// 开始
// forEach 0开始
// forEach 1开始
// forEach 2开始
// 结束
// (await结果随机出现)
// forEach 1结束
// forEach 0结束
// forEach 2结束3. for...of 循环
// 基本语法
for (const item of [0, 1, 2]) {
// 循环体
}异步特性分析:
console.log('开始');
for (const item of [0, 1, 2]) {
console.log(`for...of ${item} 开始`);
// 可以使用await
await new Promise(resolve =>
setTimeout(resolve, 100)
);
console.log(`for...of ${item} 结束`);
}
console.log('结束');
// 输出顺序:
// 开始
// for...of 0开始
// (等待100ms)
// for...of 0结束
// for...of 1开始
// (等待100ms)
// for...of 1结束
// for...of 2开始
// (等待100ms)
// for...of 2结束
// 结束4. for...in 循环
// 基本语法
for (const key in object) {
// 循环体
}异步特性分析:
const obj = { a: 1, b: 2, c: 3 };
console.log('开始');
for (const key in obj) {
console.log(`for...in ${key} 开始`);
// 遍历对象属性,同样支持await
await new Promise(resolve =>
setTimeout(resolve, Math.random() * 100)
);
console.log(`for...in ${key} 结束`);
}
console.log('结束');
// 输出顺序:顺序执行,但遍历顺序可能因JS引擎而异三、循环与 Promise 的配合方式
1. 串行执行(一个接一个)
// 方法1:使用 async/await + for...of
async function serialExecution() {
const items = [1, 2, 3];
for (const item of items) {
console.log(`开始处理 ${item}`);
// 等待当前Promise完成后再继续下一个
const result = await processItem(item);
console.log(`完成处理 ${item}: ${result}`);
}
console.log('所有任务串行完成');
}
// 方法2:使用 reduce 链式调用
function serialExecutionWithReduce() {
const items = [1, 2, 3];
return items.reduce((promiseChain, item) => {
return promiseChain.then(() => {
console.log(`开始处理 ${item}`);
return processItem(item);
}).then(result => {
console.log(`完成处理 ${item}: ${result}`);
});
}, Promise.resolve())
.then(() => console.log('所有任务串行完成'));
}
// 串行执行示例
const processItem = (item) =>
new Promise(resolve =>
setTimeout(() => resolve(`结果${item}`), 100)
);
// 输出顺序:按顺序一个个执行,总耗时 = 每个任务耗时之和2. 并行执行(同时开始)
// 方法1:使用 Promise.all
async function parallelExecution() {
const items = [1, 2, 3];
console.log('所有任务同时开始');
// 同时启动所有Promise
const promises = items.map(item => {
console.log(`启动任务 ${item}`);
return processItem(item);
});
// 等待所有Promise完成
const results = await Promise.all(promises);
console.log('所有任务并行完成:', results);
}
// 方法2:使用 forEach(但无法获取结果)
function parallelExecutionWithForEach() {
const items = [1, 2, 3];
items.forEach(async (item) => {
const result = await processItem(item);
console.log(`任务 ${item} 完成: ${result}`);
});
console.log('所有任务已启动(但无法等待全部完成)');
}
// 并行执行示例
const processItem = (item) =>
new Promise(resolve =>
setTimeout(() => resolve(`结果${item}`), Math.random() * 200)
);
// 输出顺序:同时开始,完成顺序随机,总耗时 = 最慢的任务耗时3. 限制并发数(同时执行N个)
// 方法1:使用 async-pool 库思想
async function concurrentExecution(concurrency = 2) {
const items = [1, 2, 3, 4, 5];
const results = [];
const executing = [];
for (const item of items) {
// 创建Promise
const p = processItem(item).then(result => {
results.push({ item, result });
console.log(`任务 ${item} 完成`);
});
// 保存Promise引用
const e = p.then(() =>
executing.splice(executing.indexOf(e), 1)
);
executing.push(e);
// 如果达到并发限制,等待其中一个完成
if (executing.length >= concurrency) {
await Promise.race(executing);
}
}
// 等待所有剩余任务完成
await Promise.all(executing);
console.log('所有任务完成:', results);
}
// 方法2:使用更简洁的实现
async function concurrentExecutionSimple(items, concurrency = 2) {
const batches = [];
for (let i = 0; i < items.length; i += concurrency) {
batches.push(items.slice(i, i + concurrency));
}
for (const batch of batches) {
// 并行执行当前批次
const promises = batch.map(item => processItem(item));
const results = await Promise.all(promises);
console.log(`批次完成:`, results);
}
}
// 示例:同时最多执行2个任务
const processItem = (item) =>
new Promise(resolve =>
setTimeout(() => {
console.log(`处理中: ${item}`);
resolve(`结果${item}`);
}, 1000)
);
// 输出:同时执行2个,完成一批再执行下一批4. 循环中处理错误的策略
// 方法1:串行执行,一个失败不影响后续
async function serialWithErrorHandling() {
const items = [1, 2, 3, 'error', 5];
const results = [];
for (const item of items) {
try {
const result = await processWithError(item);
results.push({ item, result });
} catch (error) {
console.error(`任务 ${item} 失败:`, error.message);
results.push({ item, error: error.message });
}
}
console.log('串行完成,有错误继续执行:', results);
}
// 方法2:并行执行,使用 allSettled
async function parallelWithErrorHandling() {
const items = [1, 2, 3, 'error', 5];
const promises = items.map(item =>
processWithError(item)
.then(result => ({ status: 'fulfilled', value: result }))
.catch(error => ({ status: 'rejected', reason: error.message }))
);
const results = await Promise.allSettled(promises);
console.log('并行完成,处理所有结果:', results);
}
// 模拟可能失败的任务
const processWithError = (item) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (item === 'error') {
reject(new Error('故意失败'));
} else {
resolve(`结果${item}`);
}
}, 100);
});四、不同循环方式在异步场景下的详细对比
对比表格
| 循环方式 | 是否支持 await | 执行顺序 | 适合场景 | 性能特点 |
|---|---|---|---|---|
| 普通 for | ✅ 支持 | 顺序执行 | 需要索引控制 | 最快,但需要手动处理异步 |
| for...of | ✅ 支持 | 顺序执行 | 需要顺序执行的异步 | 支持异步,语法简洁 |
| forEach | ❌ 不支持(async无效) | 并发启动 | 纯同步或不需要等待 | 无法等待异步完成 |
| for...in | ✅ 支持 | 顺序执行(属性顺序不确定) | 遍历对象属性 | 遍历对象时使用 |
| map | ❌ 不支持(但可返回Promise数组) | 并发启动 | 需要转换数组并并行处理 | 返回新数组,适合Promise.all |
实际场景示例对比
// 场景:处理API请求数组
const apiUrls = [
'https://api.example.com/1',
'https://api.example.com/2',
'https://api.example.com/3'
];
// 方案1:forEach ❌(错误示范)
apiUrls.forEach(async (url) => {
const data = await fetch(url).then(r => r.json()); // 无法正确等待
console.log(data); // 可能不会按预期执行
});
console.log('forEach结束'); // 会立即执行
// 方案2:for...of ✅(正确示范)
async function processWithForOf() {
for (const url of apiUrls) {
const data = await fetch(url).then(r => r.json());
console.log('for...of:', data); // 顺序执行,等待每个完成
}
console.log('for...of结束'); // 所有完成后执行
}
// 方案3:Promise.all + map ✅(并行正确示范)
async function processWithPromiseAll() {
const promises = apiUrls.map(url =>
fetch(url).then(r => r.json())
);
const results = await Promise.all(promises);
console.log('Promise.all结果:', results); // 所有结果数组
console.log('Promise.all结束'); // 所有完成后执行
}
// 方案4:reduce实现串行 ✅
async function processWithReduce() {
await apiUrls.reduce(async (prevPromise, url) => {
await prevPromise; // 等待上一个完成
const data = await fetch(url).then(r => r.json());
console.log('reduce:', data);
return data; // 传递给下一个迭代
}, Promise.resolve());
console.log('reduce结束');
}五、性能对比与最佳实践
性能测试代码
// 性能测试函数
async function performanceTest() {
const items = Array.from({ length: 100 }, (_, i) => i);
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// 测试1:串行执行
console.time('serial-for-of');
for (const item of items) {
await delay(10);
}
console.timeEnd('serial-for-of'); // ~1000ms
// 测试2:并行执行
console.time('parallel-promise-all');
const promises = items.map(() => delay(10));
await Promise.all(promises);
console.timeEnd('parallel-promise-all'); // ~10ms
// 测试3:forEach(不等待)
console.time('forEach');
items.forEach(async () => {
await delay(10);
});
console.timeEnd('forEach'); // <1ms(但不保证完成)
}
// 测试结果分析
// 串行:总时间 = 单个任务时间 × 任务数量
// 并行:总时间 ≈ 最慢的任务时间
// forEach:只测量启动时间,不测量完成时间最佳实践总结
// 规则1:需要顺序执行时 → 使用 for...of 或 for + await
async function processSequentially(items) {
for (const item of items) {
const result = await processItem(item); // 等待前一个完成
// 处理结果
}
}
// 规则2:需要并行执行时 → 使用 Promise.all + map
async function processInParallel(items) {
const promises = items.map(item => processItem(item));
const results = await Promise.all(promises); // 同时等待所有
// 处理所有结果
}
// 规则3:需要限制并发时 → 使用自定义并发控制
async function processWithConcurrency(items, limit = 5) {
const batches = [];
for (let i = 0; i < items.length; i += limit) {
batches.push(items.slice(i, i + limit));
}
for (const batch of batches) {
await Promise.all(batch.map(processItem));
}
}
// 规则4:需要处理错误且继续 → 使用 try-catch 或 allSettled
async function processWithErrorHandling(items) {
// 方式1:逐个处理错误
for (const item of items) {
try {
await processItem(item);
} catch (error) {
console.error('失败但继续:', error);
}
}
// 方式2:并行处理所有结果
const results = await Promise.allSettled(
items.map(item => processItem(item))
);
}
// 规则5:避免在forEach中使用async
// ❌ 错误
items.forEach(async (item) => {
await processItem(item); // 不会等待
});
// ✅ 正确
for (const item of items) {
await processItem(item); // 会等待
}六、高级应用场景
1. 带超时控制的循环处理
async function processWithTimeout(items, timeout = 5000) {
const results = [];
for (const item of items) {
try {
// 创建超时Promise
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('超时')), timeout)
);
// 竞速:业务Promise vs 超时Promise
const result = await Promise.race([
processItem(item),
timeoutPromise
]);
results.push({ item, result, status: 'success' });
} catch (error) {
results.push({ item, error: error.message, status: 'timeout' });
}
}
return results;
}2. 带进度报告的循环处理
async function processWithProgress(items, onProgress) {
const total = items.length;
let completed = 0;
// 并行处理但跟踪进度
const promises = items.map((item, index) =>
processItem(item).then(result => {
completed++;
onProgress({
completed,
total,
percent: Math.round((completed / total) * 100),
current: item,
index
});
return result;
})
);
const results = await Promise.all(promises);
onProgress({ completed: total, total, percent: 100 });
return results;
}
// 使用
processWithProgress([1, 2, 3, 4, 5], (progress) => {
console.log(`进度: ${progress.percent}%`);
});3. 递归异步循环
async function recursiveAsyncProcess(items, index = 0, results = []) {
if (index >= items.length) {
return results;
}
const item = items[index];
const result = await processItem(item);
results.push(result);
// 递归调用下一个
return recursiveAsyncProcess(items, index + 1, results);
}
// 尾递归优化版本
async function tailRecursiveAsyncProcess(items, index = 0, accumulator = []) {
if (index >= items.length) {
return accumulator;
}
const result = await processItem(items[index]);
accumulator.push(result);
// 直接返回递归调用
return tailRecursiveAsyncProcess(items, index + 1, accumulator);
}七、总结与决策树
选择循环方式的决策流程:
是否需要在循环中使用 await?
├── 是 → 选择 for...of 或 普通for循环
└── 否 →
├── 是否需要并行执行所有任务?
│ ├── 是 → 使用 map + Promise.all
│ └── 否 →
│ ├── 是否需要遍历对象属性?
│ │ ├── 是 → 使用 for...in
│ │ └── 否 → 使用 forEach
└── 是否需要限制并发数?
├── 是 → 使用并发控制函数
└── 否 → 已解决
是否需要在任务失败时继续执行?
├── 是 → 使用 try-catch(串行)或 Promise.allSettled(并行)
└── 否 → 直接使用
是否需要获取所有结果?
├── 是 → 确保使用返回结果的模式
└── 否 → 可以选择只执行的模式核心记忆点
forEach 中的 async 是无效的,它不会等待异步操作完成
for...of 支持 await,可以实现真正的异步顺序执行
Promise.all + map 是最常用的并行执行模式
并发控制 需要手动实现,避免资源耗尽
错误处理 要根据场景选择 try-catch 或 allSettled
正确理解和应用这些循环与Promise的配合方式,可以有效管理异步操作,避免常见的并发问题和性能瓶颈。
总结
到此这篇关于JavaScript循环中异步处理之for、forEach、for...of、for...in区别详解的文章就介绍到这了,更多相关JS循环异步处理for、forEach、for...of、for...in内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
