阿里云主机折上折
  • 微信号
Current Site:Index > Asynchronous iterator (for-await-of)

Asynchronous iterator (for-await-of)

Author:Chuan Chen 阅读数:7862人阅读 分类: JavaScript

ECMAScript 9 introduced asynchronous iterators (Async Iterators) and the for-await-of loop, providing a more intuitive syntax for handling asynchronous data streams. This feature allows developers to write asynchronous code in a synchronous style, which is particularly useful for scenarios like paginated APIs, streaming data, or any asynchronous operations that require incremental processing.

Basic Concepts of Asynchronous Iterators

Asynchronous iterators are the async counterparts of regular iterators. They return an async iterator object via the Symbol.asyncIterator method. The next() method of an asynchronous iterator returns a Promise that resolves to an object with value and done properties.

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

The for-await-of Loop

for-await-of is a loop syntax specifically designed for asynchronous iterators. It automatically waits for each Promise to resolve before proceeding to the next iteration. Here’s a simple example:

async function processAsyncData() {
  for await (const item of asyncIterable) {
    console.log(item); // Outputs 0, 1, 2 sequentially
  }
}

processAsyncData();

Practical Use Cases

Fetching Data from Paginated APIs

Consider a paginated API where each request returns a page of data. Asynchronous iterators make it easy to fetch data page by page:

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);
  }
})();

Processing Streaming Data

Node.js’s ReadableStream also supports asynchronous iteration, making it convenient to handle large files or network streams:

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');

Error Handling

Errors during asynchronous iteration can be caught using try-catch:

async function* generateWithError() {
  yield 1;
  throw new Error('Something went wrong');
  yield 2; // Will not execute
}

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

Differences from Synchronous Iterators

  1. Asynchronous iterators use Symbol.asyncIterator instead of Symbol.iterator.
  2. The next() method returns a Promise.
  3. for-await-of must be used instead of for-of.
  4. Asynchronous iterators can perform async operations during each iteration.

Implementing Custom Async Iterables

Here’s a complete example of simulating an asynchronous data source:

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); // Outputs a letter every second
  }
})();

Performance Considerations

When using asynchronous iterators, keep in mind:

  • Sequential execution may create performance bottlenecks.
  • Numerous small async operations may introduce overhead.
  • Parallel processing might be more efficient in some scenarios.

Browser and Node.js Support

Modern browsers and Node.js (v10+) support asynchronous iterators. In unsupported environments, tools like Babel can be used for transpilation. Support can be checked with:

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

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

Comparison with Other Async Patterns

Compared to Promise.all and async/await, asynchronous iterators are better suited for:

  • Scenarios with unknown or potentially infinite data.
  • Cases requiring incremental data processing.
  • Memory-constrained environments (since all data doesn’t need to be loaded at once).
// Traditional pagination handling
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;
}

// Using async iterators saves memory
async function processPages(url) {
  for await (const page of fetchPaginatedData(url)) {
    // Process page by page without storing all data
  }
}

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

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.