In this lesson, you will learn how to create your own "stream-like" data sources by combining generator functions with asynchronous iteration.
The Power of On-Demand Data
Up until now, you have seen how async/await helps us handle single values. But what if you need to process a sequence of data that arrives over time, like reading lines from a large file or chunks from a network socket?
An Async Iterator allows you to consume data piece-by-piece, pausing execution between each item. By using an async generator (a function defined with async function*), you gain the ability to "yield" promises. This creates a powerful bridge where the code producing the data stays suspended until the consumer is ready for the next piece.
Building a Custom Data Consumer
Imagine we want to simulate a stream of data packets that arrive at different times. Instead of loading them all at once (which consumes memory), we generate them on the fly:
async function* dataStream() {
const chunks = ['Part 1', 'Part 2', 'Part 3'];
for (const chunk of chunks) {
// Simulate an async delay, like waiting for I/O
await new Promise(resolve => setTimeout(resolve, 500));
yield chunk;
}
}
// Consuming the generator using for-await-of
(async () => {
for await (const piece of dataStream()) {
console.log('Received:', piece);
}
})();
How it Works
async function*: This syntax marks the function as a generator that can handle asynchronous pauses.yield: This acts as a checkpoint. It sends the current value to the consumer and pauses the function entirely.for await...of: This is the consumer syntax. It automatically handles the internal promise resolution, waiting for eachyieldto finish before moving to the next iteration.
This pattern is highly efficient because it treats the data as a flow rather than a static array. Your application only holds one "chunk" in memory at a time, making it perfect for handling large datasets without blocking the Event Loop.
Key Takeaway: Async generators combined with for await...of allow you to process sequences of data asynchronously and memory-efficiently, effectively turning any logic into a custom, stream-like data provider.