Asynchronous iterator (for-await-of)
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
- Asynchronous iterators use
Symbol.asyncIterator
instead ofSymbol.iterator
. - The
next()
method returns a Promise. for-await-of
must be used instead offor-of
.- 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