JavaScript循环中异步处理之for、forEach、for...of、for...in的区别详解

 更新时间:2026年03月03日 09:58:53   作者: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 2

2. 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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • uniapp时间格式和距离格式的转换

    uniapp时间格式和距离格式的转换

    这篇文章主要介绍了uniapp时间格式和距离格式的转换,第一种是把  YYYY-MM-DD hh:mm:ss 转换成 MM月DD日,第二种是把  hh:mm:ss 转换成 hh:mm,本文给大家介绍的非常详细,需要的朋友可以参考下
    2023-09-09
  • 浅析JS运动

    浅析JS运动

    这篇文章主要介绍了JS运动的实现原理,介绍了JS多种运动方式,希望大家仔细学习,感兴趣的小伙伴们可以参考一下
    2015-12-12
  • Bootstrap FileInput实现图片上传功能

    Bootstrap FileInput实现图片上传功能

    这篇文章主要为大家详细介绍了Bootstrap FileInput实现图片上传功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-01-01
  • JavaScript函数、方法、对象代码

    JavaScript函数、方法、对象代码

    函数定义可以嵌套在其他函数中,常用作子函数。但不能出现在循环或条件语句中。
    2008-10-10
  • JavaScript生成唯一ID的几种常用方法

    JavaScript生成唯一ID的几种常用方法

    这篇文章主要介绍了JavaScript生成唯一ID的几种常用方法,UUID 是通用唯一标识符的缩写,是由一个可以确保全球唯一性的算法生成的标识符,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-08-08
  • TensorFlow.js机器学习预测鸢尾花种类

    TensorFlow.js机器学习预测鸢尾花种类

    TensorFlow.js是一个开源的基于硬件加速的JavaScript库,用于训练和部署机器学习模型。本教程将会带大家简单了解和使用TensorFlow.js框架实现预测鸢尾花种类
    2022-11-11
  • HTML+JavaScript实现扫雷小游戏

    HTML+JavaScript实现扫雷小游戏

    这篇文章主要为大家详细介绍了HTML+JavaScript实现扫雷小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-09-09
  • JavaScript宏任务和微任务区别介绍

    JavaScript宏任务和微任务区别介绍

    这篇文章主要介绍了JavaScript宏任务和微任务区别介绍,js中的任务,大致分为2类,一类是同步任务,另一类是异步任务。而异步任务,又分为宏任务和微任务,这两个任务是两个队列,所以是先进先出的
    2022-07-07
  • 一次记住JavaScript的6个正则表达式方法

    一次记住JavaScript的6个正则表达式方法

    这篇文章主要介绍了一次记住JavaScript的6个正则表达式方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2018-02-02
  • 原生js实现抽奖小游戏

    原生js实现抽奖小游戏

    这篇文章主要为大家详细介绍了原生js实现抽奖小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-06-06

最新评论