Mastering Asynchronous Iteration in JavaScript: Practical Patterns and Use Cases
Learn how to use asynchronous iteration in JavaScript with practical examples and patterns suitable for beginners.
Asynchronous iteration is a powerful feature in JavaScript that helps you handle data streams, network requests, and other asynchronous tasks in a clean, manageable way. Unlike traditional loops that expect synchronous data, asynchronous loops can wait for promises to resolve before moving to the next item. This tutorial covers the basics of asynchronous iteration and practical examples you can use in real projects.
The primary tool for asynchronous iteration in JavaScript is the `for await...of` loop. It works like the regular `for...of` loop but waits for each promise in the iterable to resolve before continuing. This is especially useful when you process streams of data asynchronously or consume APIs that return promises.
async function* asyncGenerator() {
const data = [1000, 2000, 1500];
for (const delay of data) {
// Simulate an async operation with a delay
await new Promise(resolve => setTimeout(resolve, delay));
yield `Waited for ${delay} ms`;
}
}
(async () => {
for await (const message of asyncGenerator()) {
console.log(message);
}
})();In this example, `asyncGenerator` is an asynchronous generator function that simulates waiting for different delays. The `for await...of` loop consumes these delayed messages one by one, logging them after the awaited delay completes. This pattern is great when you need to process asynchronous data sequentially without blocking.
You can also use asynchronous iteration with real-world data such as fetching multiple URLs. Instead of firing all fetch requests at once, you can wait for each response in order, which can be valuable if the sequence matters.
async function* fetchUrls(urls) {
for (const url of urls) {
const response = await fetch(url);
const text = await response.text();
yield text;
}
}
(async () => {
const urls = [
'https://jsonplaceholder.typicode.com/posts/1',
'https://jsonplaceholder.typicode.com/posts/2'
];
for await (const content of fetchUrls(urls)) {
console.log('Fetched content length:', content.length);
}
})();Notice how the generator function `fetchUrls` waits for each fetch request to complete and yield the text content. The `for await...of` loop then handles these promises in sequence, making the code easy to read and maintain.
Another practical use case is processing data from a readable stream — like a file or a network socket asynchronously. Streams can be iterated with `for await...of` to handle chunks of data as they arrive.
async function processStream(stream) {
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log('Received chunk:', new TextDecoder().decode(value));
}
}If you want to use the built-in async iterable support of streams, you can simplify the code further:
async function processStreamAsync(stream) {
for await (const chunk of stream) {
console.log('Received chunk:', new TextDecoder().decode(chunk));
}
}### Summary - Use `async function*` to create asynchronous generators. - Use `for await...of` loops to consume async iterables. - These patterns help you work with multiple asynchronous operations in sequence without complex promise chains. - Practical examples include delayed events, fetching URLs, or processing streams incrementally. Mastering asynchronous iteration will make your JavaScript code more readable and efficient when dealing with asynchronous workflows.