阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 异步迭代器(for-await-of)

异步迭代器(for-await-of)

作者:陈川 阅读数:7861人阅读 分类: JavaScript

ECMAScript 9 引入了异步迭代器(Async Iterators)和 for-await-of 循环,为处理异步数据流提供了更直观的语法。这一特性使得开发者能够以同步的方式编写异步代码,尤其在处理分页API、流式数据或任何需要逐步解析的异步操作时非常有用。

异步迭代器的基础概念

异步迭代器是普通迭代器的异步版本,它通过 Symbol.asyncIterator 方法返回一个异步迭代器对象。异步迭代器的 next() 方法返回一个 Promise,该 Promise 解析为一个包含 valuedone 属性的对象。

const asyncIterable = {
  [Symbol.asyncIterator]() {
    let i = 0;
    return {
      next() {
        if (i < 3) {
          return Promise.resolve({ value: i++, done: false });
        }
        return Promise.resolve({ done: true });
      }
    };
  }
};

for-await-of 循环

for-await-of 是专门为异步迭代器设计的循环语法,它会自动等待每个 Promise 解析后再继续下一次迭代。以下是一个简单的示例:

async function processAsyncData() {
  for await (const item of asyncIterable) {
    console.log(item); // 依次输出 0, 1, 2
  }
}

processAsyncData();

实际应用场景

分页API的数据获取

假设有一个分页API,每次请求返回一页数据。使用异步迭代器可以轻松实现逐页获取数据的功能:

async function* fetchPaginatedData(url) {
  let page = 1;
  while (true) {
    const response = await fetch(`${url}?page=${page}`);
    const data = await response.json();
    if (data.length === 0) break;
    yield data;
    page++;
  }
}

(async () => {
  for await (const pageData of fetchPaginatedData('https://api.example.com/items')) {
    console.log('Received page:', pageData);
  }
})();

流式数据处理

Node.js 中的 ReadableStream 也支持异步迭代,可以方便地处理大文件或网络流:

import { createReadStream } from 'fs';

async function processFile(filePath) {
  const stream = createReadStream(filePath, { encoding: 'utf8' });
  for await (const chunk of stream) {
    console.log('Received chunk:', chunk);
  }
}

processFile('./large-file.txt');

错误处理

在异步迭代过程中,错误可以通过 try-catch 捕获:

async function* generateWithError() {
  yield 1;
  throw new Error('Something went wrong');
  yield 2; // 不会执行
}

(async () => {
  try {
    for await (const num of generateWithError()) {
      console.log(num);
    }
  } catch (err) {
    console.error('Caught error:', err.message);
  }
})();

与同步迭代器的区别

  1. 异步迭代器使用 Symbol.asyncIterator 而非 Symbol.iterator
  2. next() 方法返回 Promise
  3. 必须使用 for-await-of 而非 for-of 循环
  4. 异步迭代器可以在每次迭代中执行异步操作

实现自定义异步可迭代对象

以下是一个模拟异步数据源的完整示例:

class AsyncDataSource {
  constructor(data, delay) {
    this.data = data;
    this.delay = delay;
  }

  async *[Symbol.asyncIterator]() {
    for (const item of this.data) {
      await new Promise(resolve => setTimeout(resolve, this.delay));
      yield item;
    }
  }
}

(async () => {
  const asyncData = new AsyncDataSource(['a', 'b', 'c'], 1000);
  for await (const letter of asyncData) {
    console.log(letter); // 每隔1秒输出一个字母
  }
})();

性能考虑

使用异步迭代器时需要注意:

  • 顺序执行特性可能导致性能瓶颈
  • 大量小规模异步操作可能产生额外开销
  • 在某些场景下,并行处理可能更高效

浏览器和Node.js支持

现代浏览器和Node.js(v10+)都已支持异步迭代器。在不支持的环境中,可以通过Babel等工具进行转译。可以通过以下代码检测支持情况:

const isAsyncIterable = (obj) => 
  obj != null && typeof obj[Symbol.asyncIterator] === 'function';

console.log('Support:', isAsyncIterable({ [Symbol.asyncIterator]() {} }));

与其他异步模式的比较

与Promise.all和async/await相比,异步迭代器更适合:

  • 数据量未知或可能无限的情况
  • 需要逐步处理数据的场景
  • 内存受限的环境(因为不需要一次性加载所有数据)
// 传统方式处理分页
async function getAllPages(url) {
  let allData = [];
  let page = 1;
  while (true) {
    const res = await fetch(`${url}?page=${page}`);
    const data = await res.json();
    if (data.length === 0) break;
    allData = allData.concat(data);
    page++;
  }
  return allData;
}

// 使用异步迭代器更节省内存
async function processPages(url) {
  for await (const page of fetchPaginatedData(url)) {
    // 逐页处理,不保存所有数据
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌