Async Iterators & for-await-of
An async iterator is an object whose next() returns a promise of { value, done } , letting you pull values that arrive over time — one awaited step at a time — using the for-await-of loop.
Learn Async Iterators & for-await-of in our free JavaScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
They power streaming: reading a file line by line, paginating an API, or consuming a live feed without ever loading everything into memory at once.
What You'll Learn in This Lesson
📚 Prerequisites: Make sure you know async/await and promises . Async iterators glue those together with looping.
📬 Real-World Analogy: A regular array is like a stack of mail already on your desk — all there, read it whenever. An async iterator is like checking your mailbox: each trip might involve a wait for the next letter to arrive, and you only hold one letter at a time. for-await-of is you walking to the mailbox over and over until there is nothing left.
1️⃣ The Protocol — Symbol.asyncIterator
An object is async iterable if it has a method keyed by Symbol.asyncIterator that returns an async iterator — an object with a next() method that returns a promise of { value, done } . The for-await-of loop awaits each of those promises for you.
Here it is written out by hand so you can see exactly what the loop is driving:
2️⃣ Async Generators — the Easy Way
Writing the protocol by hand is tedious. async function* builds the whole thing for you: each yield produces a value, and because the function is async you can await between yields. Calling it returns a ready-to-loop async iterable.
This is the real streaming pattern — here a generator emits a tick every short delay, exactly how you might stream rows from a database cursor:
💡 Tip: The values are produced lazily . The generator is paused at each yield until the loop asks for the next one, so nothing runs ahead of what you consume.
🎯 Your Turn #1 — Loop an Async Generator
Replace the blank with the correct loop keyword to consume the async generator.
3️⃣ Real Use — Paginating an API Lazily
The killer app for async generators is pagination . Wrap the "fetch a page, yield each item, advance the cursor" loop in an async generator and the caller just writes a single for-await-of — completely unaware of pages, cursors, or network waits.
Below, a Map-backed source stands in for the network so you can watch the same pattern stream every item across "pages" — with no DOM or fetch required:
🎯 Your Turn #2 — Yield Values
Make the generator emit the squares 1, 4, 9 by replacing the blank with yield .
🧠 Quick Recall
Predict the output before revealing the answer.
1 false — next() returns a promise of { value, done } , and the first call yields 1 with done: false .
A TypeError — a plain for-of cannot drive an async iterable. You must use for await .
Symbol.asyncIterator — the sync version is Symbol.iterator .
Common Errors & Fixes
- 1️⃣ Using for-of on an async iterable
- 2️⃣ Forgetting for-await must be inside an async function
- 3️⃣ Mixing up the two symbols
- 4️⃣ Buffering an endless stream into an array
🎯 Mini Challenge — Async Filter
Build an async generator onlyEven that wraps another async iterable and yields only its even numbers. This is how you compose streaming pipelines.
Quick Reference — Async Iteration
Concept
Syntax
Async iterable hook
[Symbol.asyncIterator]() {}
Async generator
async function* g() {}
Produce a value
yield value;
Consume a stream
for await (const x of s) {}
Manual step
await it.next()
📚 Keep Learning
- • MDN — for await...of
- • MDN — AsyncGenerator
- • MDN — Symbol.asyncIterator
Frequently Asked Questions
🎉 Lesson Complete — Async Iterators!
- ✅ Async iterables expose Symbol.asyncIterator
- ✅ async function* is the easy way to build one
- ✅ for await (const x of s) consumes a stream lazily
- ✅ Perfect for pagination, file/line streaming, and live feeds
Up next: AbortController & Cancelling Async Work — stop work you no longer need. ⏹️
Practice quiz
What does an async iterator's next() return?
- A value directly
- An array
- A promise of { value, done }
- void
Answer: A promise of { value, done }. Unlike a sync iterator, an async iterator's next() returns a PROMISE of { value, done }.
Which loop consumes an async iterable?
- for-await-of
- for-of
- forEach
- while
Answer: for-await-of. for-await-of awaits each promise from next(); a plain for-of cannot drive an async iterable.
What happens if you use a plain for-of on an async iterable?
- It works fine
- It silently does nothing
- It returns promises
- A TypeError — it is not iterable that way
Answer: A TypeError — it is not iterable that way. A plain for-of cannot drive an async iterable and throws a TypeError.
Which symbol marks an object as async iterable?
- Symbol.iterator
- Symbol.asyncIterator
- Symbol.for('async')
- Symbol.next
Answer: Symbol.asyncIterator. The method keyed by Symbol.asyncIterator is what for-await-of looks for; the sync version is Symbol.iterator.
What does 'async function*' create?
- An async generator (an async iterable)
- An async function
- A normal generator
- A promise
Answer: An async generator (an async iterable). async function* builds an async generator, the easiest way to produce an async iterable.
Inside an async generator, which keyword produces each value?
- return
- await
- yield
- emit
Answer: yield. Each yield produces a value, and you can await between yields because the function is async.
Why stream with for-await-of instead of collecting everything into an array first?
- It is the only way that compiles
- Memory stays flat — each value can be released as the next arrives
- Arrays cannot hold promises
- It is slower but simpler
Answer: Memory stays flat — each value can be released as the next arrives. Processing each value as it arrives keeps memory flat even for huge or endless streams.
For await const r = await g().next() on 'async function* g(){ yield 1; yield 2; }', what is r.value and r.done?
- 1 and true
- undefined and true
- 2 and false
- 1 and false
Answer: 1 and false. The first next() yields value 1 with done: false.
for-await-of must appear inside what?
- A class method
- An async function
- A generator
- Any function works
Answer: An async function. Using for await outside an async function is a SyntaxError.
What is the killer use case for async generators highlighted in the lesson?
- Sorting arrays
- Math calculations
- Lazily paginating an API one page at a time
- DOM manipulation
Answer: Lazily paginating an API one page at a time. Wrapping fetch-a-page-and-yield-each-item in an async generator lets the caller write a single for-await-of, unaware of paging.
Continue this course
- Previous: Promise Combinators
- Next: AbortController & Cancelling Async Work