Async Iteration
This lesson explains Async Iteration with clear examples, use cases, and best practices so you can use modern JavaScript confidently in real projects.
ES6 Async Iteration Overview
Async iteration allows JavaScript to iterate over asynchronous data
sources using for await...of and asynchronous iterators.
Unlike normal iteration, async iteration waits for Promises to resolve
before processing the next value.
Async iteration is commonly used when reading streams, processing files,
paginated APIs, database cursors, Web Streams, Node.js streams, and other
asynchronous data sources.
| Feature | Description |
for await...of | Iterates over asynchronous iterables. |
| Async Iterator | Returns Promises from the next() method. |
| Async Generator | Uses async function* and yield. |
| Works With | Streams, APIs, database cursors, files, generators. |
| Best Use | Sequential processing of asynchronous values. |
Why Async Iteration?
Normally, JavaScript loops process values that already exist. However,
sometimes values arrive over time from APIs, streams, or files.
const numbers =
[1, 2, 3];
for (const number of numbers) {
console.log(number);
}
The example above works because all values already exist. Async iteration
is required when values become available asynchronously.
Using for await...of
async function processPromises() {
const promises =
[
Promise.resolve(10),
Promise.resolve(20),
Promise.resolve(30)
];
for await (const value of promises) {
console.log(value);
}
}
processPromises();
Each Promise is awaited before the next iteration begins.
Async Generator Functions
Async generators produce asynchronous values using
async function*.
async function* generateNumbers() {
yield 1;
yield 2;
yield 3;
}
async function run() {
for await (const value of generateNumbers()) {
console.log(value);
}
}
run();
Async Generator with Delay
function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function* countdown() {
for (let i = 3; i >= 1; i--) {
await wait(1000);
yield i;
}
}
async function start() {
for await (const value of countdown()) {
console.log(value);
}
}
start();
Each number is produced after a one-second delay.
Creating an Async Iterator
const asyncCollection =
{
values:
[100, 200, 300],
async *[Symbol.asyncIterator]() {
for (const value of this.values) {
yield value;
}
}
};
async function display() {
for await (const value of asyncCollection) {
console.log(value);
}
}
display();
Objects become asynchronously iterable by implementing
Symbol.asyncIterator.
Processing Paginated API Results
async function* getPages() {
let page = 1;
while (page <= 3) {
const response =
await fetch("/api/users?page=" + page);
const data =
await response.json();
yield data;
page++;
}
}
async function loadUsers() {
for await (const users of getPages()) {
console.log(users);
}
}
Async generators are useful for loading paginated API responses one page
at a time.
Reading Large Files
async function processChunks(stream) {
for await (const chunk of stream) {
console.log(chunk);
}
}
Browser streams and Node.js streams can be processed chunk by chunk using
async iteration.
Node.js Stream Example
import fs from "node:fs";
async function readFile() {
const stream =
fs.createReadStream("data.txt");
for await (const chunk of stream) {
console.log(chunk.toString());
}
}
Async iteration makes reading streams much simpler than using event
listeners.
Web Streams Example
async function downloadData() {
const response =
await fetch("/large-file");
const stream =
response.body;
for await (const chunk of stream) {
console.log(chunk);
}
}
This processes downloaded data as it arrives instead of waiting for the
complete response.
Returning Values from Async Generators
async function* numbers() {
yield 10;
yield 20;
return 30;
}
async function run() {
for await (const number of numbers()) {
console.log(number);
}
}
The returned value is not included in the
for await...of loop.
Error Handling
async function loadProducts() {
try {
for await (const product of getProducts()) {
console.log(product);
}
} catch (error) {
console.error(error.message);
}
}
Wrap asynchronous iteration in a
try...catch block to handle rejected Promises.
Sequential vs Parallel Processing
for await (const item of items) {
await process(item);
}
The code above processes one item at a time.
await Promise.all(
items.map((item) =>
process(item)
)
);
Promise.all() processes independent tasks in parallel and is
usually faster.
Real-World Examples
Upload Multiple Files
async function upload(files) {
for await (const file of files) {
console.log(
"Uploading:",
file.name
);
}
}
Database Cursor
async function processRows(cursor) {
for await (const row of cursor) {
console.log(row);
}
}
Reading Notifications
async function displayNotifications(stream) {
for await (const notification of stream) {
console.log(notification);
}
}
Iteration Comparison
| Loop | Supports Async Values | Use Case |
for | No | Simple arrays. |
for...of | No | Iterables. |
forEach() | No | Array callbacks. |
for await...of | Yes | Async iterables. |
Common Async Iteration Use Cases
- Reading paginated REST APIs.
- Processing file streams.
- Reading Node.js streams.
- Working with Web Streams.
- Database cursor processing.
- Event streams.
- Large dataset processing.
- Incremental downloads.
Common Async Iteration Mistakes
- Using
for...of instead of for await...of for async iterables. - Using
await inside forEach(). - Forgetting that async generators return Promises.
- Ignoring rejected Promises.
- Processing independent tasks sequentially when parallel execution is faster.
- Not closing streams after processing.
- Creating unnecessary async generators for simple arrays.
- Not handling cancellation or abort signals.
Async Iteration Best Practices
- Use
for await...of only with asynchronous iterables. - Use async generators for streaming data.
- Handle errors using
try...catch. - Use
Promise.all() for independent tasks. - Avoid async work inside
forEach(). - Release streams and resources when finished.
- Keep generators focused on producing data.
- Use descriptive async function names.
Key Takeaways
for await...of iterates over asynchronous values. - Async generators are declared using
async function*. - Async iterators return Promises from
next(). - Streams and paginated APIs work well with async iteration.
- Use
Promise.all() for parallel work. - Use
try...catch for error handling.
Pro Tip
Remember this interview rule:
for...of works with synchronous iterables,
for await...of works with asynchronous iterables.
If data arrives over time, use async iteration. If all values already
exist, use normal iteration.