An async iterator produces a sequence of values whose next result may take time to arrive. Like a synchronous iterator, it reports a value and whether iteration is done. The difference is that its `next()` method returns a Promise that resolves to `{value, done}`. This lets a loop wait for each result without loading an entire data source at once. section 9.6 section 8.5
Ordinary `for...of` does not wait for asynchronous results. If three delayed promises are handled with `.then()` inside that loop, their callbacks can run in completion order rather than array order. A Promise chain can coordinate them, but `for await...of` expresses the waiting directly: section 8.5
Consume delayed values
```js for await (const value of [ delay('a', 500), delay('b', 100), delay('c', 200) ]) { console.log(value); } ``` The loop processes the results as `a`, `b`, then `c`, despite their different delays. The promises have already been created; the loop controls when each result is processed. section 8.5
To make an object asynchronously iterable, define its `Symbol.asyncIterator` method to return an async iterator. Each call to that iterator’s `next()` returns a Promise for the next `{value, done}` result; a result with `done: true` ends the sequence. `for await...of` consumes the object through this protocol. section 8.5 section 9.6
Listing 8.26 Iterator object that emits values with a provided delay
function delayedIterator(tasks) {
return {
next: function () {
if (tasks.length) {
const [value, time] = tasks.shift(); #1
return new Promise(resolve => { #2
setTimeout(resolve, time, { value, done: false });
});
} else {
return Promise.resolve({
done: true #3
});
}
}
};
}
A delayed iterator makes the protocol concrete: `next()` resolves to a value-bearing result while tasks remain, then resolves to a done result. section 8.5
File streams put the protocol to work. Iterating over a Node.js read stream yields chunks, so a large file can be processed as a moving window instead of being read wholly into memory. When records can cross chunk boundaries, retain the unfinished portion and combine it with the next chunk before parsing. section 8.5
An async generator offers another way to produce an asynchronously consumed sequence. A file-processing generator can yield parsed records as they become available, leaving consumers free to count or validate them. That separates reading and parsing from the operation performed on each record. section 9.2.3 section 9.6
Listing 9.9 Using generateBlocksFromFile as an async iterator
let result = 0;
for await (const block of generateBlocksFromFile('blocks.txt')) { #1
console.log('Counting block', block.hash);
result++;
}
result; // 3
The counting loop consumes records from an async generator without containing the file-reading or parsing logic. section 9.2.3
