Generator return doesn't work in for-await-of loop
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In JavaScript, for await ... of consumes values that are yielded by an async iterator. It does not expose the final value passed to return. That behavior surprises people because the generator really does return a value, but the loop protocol treats that final step as termination, not as one more item.
What the Loop Actually Reads
An async generator produces iteration results shaped like value plus done. As long as done is false, the loop body runs. When done becomes true, the loop stops immediately.
That means this generator returns a value, but the loop never prints it:
The output is:
The "final-state" value exists only on the final iterator result where done is true.
Why return Feels Different From yield
Think of yield as publishing data to the consumer and return as ending the iterator. The returned value is part of the iterator protocol, but for await ... of is intentionally designed to hide that final record and stop.
This is consistent with synchronous generators too. The same distinction exists there, but async iteration makes it easier to forget because the code often looks like a stream API.
How to Read the Final Return Value
If you need both yielded values and the final returned value, iterate manually with next.
That code prints both the streamed values and the terminal payload.
Prefer a Final yield for Consumer-Facing Data
If downstream code is supposed to use for await ... of, the cleanest design is usually to yield the final information explicitly rather than burying it in return.
This makes the contract obvious: everything the consumer needs arrives through yielded values.
Cleanup and Early Exit
return has another important role in generators: cleanup. If the consumer stops early, the generator can still run finally blocks.
This is why return is still useful even when its value is not visible in the loop body. It remains part of how iterators shut down correctly.
A Utility Pattern for Capturing Both
If you regularly need both the emitted values and the final return value, write a helper that drives the iterator manually and packages the result.
This pattern is helpful when the final value is metadata such as a checksum, summary, or completion status.
Common Pitfalls
A common mistake is putting business-critical output into return and then consuming the iterator with for await ... of. That output disappears from the loop body by design.
Another mistake is assuming async generators behave like an event emitter. They do not. The consumer sees only yielded steps unless it manually inspects the final iterator result.
It is also easy to forget cleanup. If the generator owns resources, use try and finally so early loop termination does not leak them.
Summary
- '
for await ... ofreads yielded values only, not the final returned value.' - The final
returnpayload is available only on the iterator result wheredoneistrue. - If you need that payload, iterate manually with
next. - For consumer-visible completion data, prefer a final
yieldrather thanreturn. - Use
finallyblocks in generators to keep cleanup reliable.

