详细讲解下 for...of vs for await...of 区别

for...offor await...of 都是 ES6 之后引入的迭代语句,用于遍历可迭代对象 (Iterable)中的值。它们的核心区别在于处理的迭代协议不同:一个是同步迭代 ,另一个是异步迭代。下面从协议基础、行为差异、适用场景和底层细节展开对比。


1. 迭代协议基础

特性 for...of for await...of
依赖的迭代协议 Symbol.iterator Symbol.asyncIterator
迭代器返回的方法 next() 直接返回 { value, done } next() 返回一个 Promise<{ value, done }>
适用的可迭代对象 数组、字符串、Map、Set、arguments、NodeList、生成器对象(同步)等 异步生成器、ReadableStream、实现了 Symbol.asyncIterator 的对象、同步可迭代对象(会被自动适配)
是否可在非 async 函数内使用 ✅ 任何地方 ✅ 任何地方(但循环体内若使用 await 则必须包裹在 async 函数中)

2. 核心区别详解

2.1 迭代值的获取方式

  • for...of :同步获取下一个值,若迭代器返回的是 Promise 则不会被等待,直接将 Promise 对象作为值赋给循环变量。
javascript 复制代码
const iterable = {
  [Symbol.iterator]() {
    let i = 0;
    return {
      next() {
        if (i++ < 2) {
          return { value: Promise.resolve(i), done: false };
        }
        return { done: true };
      }
    };
  }
};

for (const x of iterable) {
  console.log(x); // Promise { <resolved>: 1 } 两次
}
  • for await...of :会等待 迭代器的 next() 返回的 Promise 完成,并将 fulfilled 的值赋给循环变量。
javascript 复制代码
const asyncIterable = {
  [Symbol.asyncIterator]() {
    let i = 0;
    return {
      async next() {
        if (i++ < 2) {
          return { value: i, done: false };
        }
        return { done: true };
      }
    };
  }
};

for await (const x of asyncIterable) {
  console.log(x); // 1, 2(数字,非 Promise)
}

2.2 对同步可迭代对象的兼容性

  • for...of 只能直接遍历同步可迭代对象。
  • for await...of 也能遍历同步可迭代对象(如数组、Set),此时它会将同步返回的 { value, done } 中的 value 自动包装成已解决的 Promise,然后等待其结果(其实瞬间完成)。
javascript 复制代码
const arr = [1, 2, 3];

// 正常打印 1, 2, 3
for (const v of arr) console.log(v);

// 同样正常打印 1, 2, 3,但有细微的异步开销
for await (const v of arr) console.log(v);

因此 for await...of 是一个更宽松的循环,它既能处理异步迭代器,也能处理同步迭代器(只不过在同步迭代器上会多一次 Promise 转换和 microtask 调度)。

2.3 循环体内的异步操作支持

  • for...of 循环体内部可以使用 await,但前提是整个循环必须位于 async 函数内 ,且 await 并不会影响迭代器获取下一个值------循环仍然同步推进,只是循环体内的异步操作被挂起。
javascript 复制代码
async function demo() {
  for (const url of urls) {
    const data = await fetch(url); // 每次迭代等待 fetch 完成,但迭代本身是同步推进的
  }
}
  • for await...of在每次迭代获取下一个值时自动等待 (即等待 next() 的 Promise),循环体内部可以继续使用 await 做其他异步操作。

2.4 错误处理

  • for...of 的迭代器抛出同步异常,会被 try...catch 捕获。
  • for await...of 的迭代器 next() 返回的 Promise 被拒绝,或异步生成器内部抛出异常,该异常也会在循环内被捕获(因为循环本身会 await 该 Promise)。

3. 适用场景对比

✅ 使用 for...of 的场景

  • 遍历同步数据结构:数组、字符串、Map、Set、TypedArray、arguments、DOM 集合等。
  • 处理同步生成器函数返回的生成器对象。
  • 循环体需要执行异步操作,但获取下一个迭代值的时机不依赖异步结果

✅ 使用 for await...of 的场景

  • 遍历异步数据流

    • ReadableStream(浏览器流 API、Node.js 流通过 stream[Symbol.asyncIterator]
    • 异步生成器函数(async function*)返回的对象
    • 分页 API 数据,需要逐页等待获取下一页
  • 处理以 Promise 形式逐个提供值的数据源。

  • 需要顺序处理异步任务,且每个任务的触发依赖于上一个任务完成后的状态(例如数据库游标逐条读取)。


4. 底层协议深入

Symbol.iterator vs Symbol.asyncIterator

协议 方法名 next() 返回值 return() / throw() 返回值
同步迭代器 [Symbol.iterator] { value, done } { value, done } 或抛出同步异常
异步迭代器 [Symbol.asyncIterator] Promise<{ value, done }> Promise<{ value, done }>

for await...of 实际上在引擎内部会调用对象的 [Symbol.asyncIterator] 方法,若该方法不存在,则会回退[Symbol.iterator],并将同步返回的值包装成 Promise.resolve(value)。这也是为什么 for await...of 能遍历同步可迭代对象。


5. 示例对比

示例 1:遍历异步生成器

javascript 复制代码
async function* generateNumbers() {
  yield 1;
  yield 2;
  yield 3;
}

// ❌ 错误:TypeError: generateNumbers() is not iterable
for (const num of generateNumbers()) { }

// ✅ 正确
for await (const num of generateNumbers()) {
  console.log(num); // 1, 2, 3
}

示例 2:遍历数组(对比异步等待行为)

javascript 复制代码
const urls = ['/api/1', '/api/2', '/api/3'];

// 在 async 函数中用 for...of
async function fetchAll() {
  for (const url of urls) {
    const res = await fetch(url); // 循环等待每次 fetch 完成才进入下一次迭代
    console.log(await res.json());
  }
}

// 用 for await...of 遍历同一个数组(没有必要但合法)
async function fetchAll2() {
  for await (const url of urls) {
    const res = await fetch(url);
    console.log(await res.json());
  }
}
// 两者行为相同,但 fetchAll2 每次迭代会额外创建一个 Promise 包装 url 字符串

示例 3:读取流数据(Node.js 可读流)

javascript 复制代码
import { createReadStream } from 'fs';

const stream = createReadStream('./file.txt', { encoding: 'utf8' });

for await (const chunk of stream) {
  console.log(chunk);
}

6. 性能与注意事项

  • 性能差异for await...of 遍历同步可迭代对象时,由于需要将每个值包装成 Promise 并在 microtask 中展开,会带来额外开销。不建议for await...of 用于纯同步数据的遍历。
  • 不能在普通对象上使用 :两者都要求对象实现对应的迭代器协议,普通对象 {} 既没有 [Symbol.iterator] 也没有 [Symbol.asyncIterator],因此不能直接使用 for...offor await...of
  • for await...of 与循环内 await 的关系 :即使没有在循环体内显式写 awaitfor await...of 本身也会在每次迭代时对 next() 的结果进行 await,因此整体执行是异步的。这意味着循环后的代码会在循环完全结束后才执行。

7. 总结对照表

维度 for...of for await...of
迭代协议 Symbol.iterator Symbol.asyncIterator(优先)
处理异步值 不等待,直接得到 Promise 对象 等待 Promise 解析后得到值
遍历同步可迭代对象 ✅ 原生支持 ✅ 支持(有额外 Promise 包装)
遍历异步可迭代对象 ❌ 报错或得到 Promise 对象 ✅ 原生支持
主要用途 同步数据遍历 异步数据流、异步生成器遍历
循环体内 await 的影响 仅暂停循环体,不改变迭代推进时机 既等待迭代器 Promise,也可在循环体内 await
错误传播 同步异常直接抛出 异步拒绝被捕获为异常

一句话总结:
for...of 用于同步迭代值;for await...of 用于异步迭代值,它会在每次迭代时自动等待 Promise 解析,适合处理流式数据、异步生成器以及需要按顺序等待异步结果的场景。

相关推荐
Csvn8 小时前
OpenSpec 详细使用教程
前端
之歆9 小时前
Day19_LESS 完全指南——从入门到工程实践
前端·css·less
云水一下10 小时前
HTML5 从入门到精通:实战收官——从零搭建完整静态网站,综合运用所有知识
前端·html5
不总是10 小时前
Windows 系统 Node.js 免安装版(zip)安装与配置教程(2026 最新)
前端·windows·node.js
冬奇Lab10 小时前
每日一个开源项目(第105篇):Twenty - 跳出 Salesforce 的圈套,定义现代开源 CRM
前端·后端·开源
zhangyao94033011 小时前
开发pc端时,表格的高度怎么设置才能铺满页面
前端·javascript·elementui
kjs--11 小时前
浏览器书签执行脚本
前端
之歆11 小时前
Day16_JavaScript 轮播图与事件工程实战(下篇)
服务器·开发语言·前端·javascript·网络·性能优化
沄媪12 小时前
CSRF 跨站请求伪造
前端·ctf·csrf
kyriewen12 小时前
我关掉了Copilot:因为我写的代码出现在了别人的建议里
前端·javascript·ai编程