JavaScript Generator Functions
Learn JavaScript generators: the function* syntax, yield and yield*, sending values with next(), return() and throw(), lazy infinite sequences.
A generator is a special kind of function that can pause itself in the middle, hand a value back to the caller, and then resume exactly where it left off the next time you ask for more. Instead of computing everything up front and returning once, a generator produces a sequence of values lazily — one at a time, only when requested.
That single idea makes generators the cleanest way in JavaScript to build custom iterables, model infinite sequences without running out of memory, and step through long-running logic on demand. This guide covers the function* syntax, yield and yield*, two-way communication with next(), early termination, and the practical patterns where generators shine.
This page builds on iterables and iterators — if the iterator protocol is new to you, read that first.
Understanding Generator Functions
The Basics of Generator Functions
A generator function is declared like a regular function but with a star after the function keyword: function*. The star is what marks the function as a generator.
Calling a generator function does not run its body. Instead it returns a generator object — an iterator you drive manually. Each call to its next() method runs the body up to the next yield, then pauses and returns { value, done }:
The first next() runs until the first yield 'Hello' and pauses there. The second resumes after that line and runs until yield 'World'. The third resumes, finds no more yield statements, reaches the end of the function, and reports done: true. Calling next() again would keep returning { value: undefined, done: true }.
Because a generator object is also iterable, you can consume it with for...of, the spread operator, or destructuring — all of which stop automatically when done becomes true:
A generator object can only be iterated once. After it is exhausted, for...of produces nothing. Call the generator function again to get a fresh generator.
A generator's return value
A return inside a generator ends the iteration and supplies the final value together with done: true. Note that for...of ignores this returned value — it only sees the yielded ones:
Controlling the Flow with return() and throw()
Besides next(), a generator object exposes two more methods that let the caller steer execution from the outside:
generator.return(value)forces the generator to finish immediately, running anyfinallyblocks on the way out and returning{ value, done: true }.generator.throw(error)injects an exception at the currentyieldpoint, so the generator can catch it withtry...catch— or propagate it if it doesn't.
The finally block makes generators a tidy place to release resources (close a file, disconnect a stream) even when iteration is abandoned early.
Advanced Generator Patterns
Delegating with yield*
When one generator needs to produce the values of another generator or any iterable, use yield* (read "yield-delegate") instead of writing a manual loop. It transparently forwards every value from the delegated source:
A useful detail: the expression value of yield* is the return value of the inner generator, which lets you compose generators and pass a result back to the outer one:
Sending Values into Generators
Communication with a generator is two-way. A yield expression not only sends a value out through next(), it also evaluates to whatever you pass into the next next(value) call. This turns a generator into a small interactive coroutine:
The first next() runs up to the first yield and returns the question; its argument is discarded because there is no paused yield to receive it yet. The second next('Alice') resumes the paused yield, so the expression becomes 'Alice' and gets assigned to name.
Practical Applications of JavaScript Generators
Building Custom Iterables
The most common real-world use of generators is to make your own objects iterable. Define [Symbol.iterator] as a generator method, and any for...of, spread, or destructuring will work on the object. This is far less boilerplate than writing an iterator object with a hand-rolled next() by hand (see Symbol type for what Symbol.iterator is):
Lazy and Infinite Sequences
Because a generator computes each value only when asked, it can describe a sequence that is conceptually infinite without ever exhausting memory. The classic example is a counter, but the same approach builds ranges, ID generators, or Fibonacci streams. You combine it with a "take" helper that stops after the count you need:
A parameterised range generator is a handy, reusable variant of the same idea:
Managing Asynchronous Operations
Historically, generators were used to flatten "callback hell": each yield paused on a Promise, and an external runner resumed the generator once that Promise resolved. The example below shows the manual mechanics so you can see what is happening under the hood:
This is a simplified manual runner. In modern code you would write this with async/await, which the language built directly on top of this generator-plus-Promise pattern. For asynchronous iteration (streaming chunks over time) use async generators with async function* and for await...of.
Generators vs. Regular Functions
| Aspect | Regular function | Generator function |
|---|---|---|
| Declaration | function fn() | function* fn() |
| Calling it | runs the body to completion | returns a generator object, runs nothing yet |
| Returns | a single value | a stream of values via yield |
| Can pause? | no | yes, at every yield |
| Iterable? | no | yes (works with for...of, spread) |
Reach for a generator when you need values produced lazily or on demand, an object to be iterable, or a sequence that may be infinite. For a plain one-shot computation, a regular function is simpler and faster.
Conclusion
Generators give JavaScript a clean way to produce sequences lazily, pause and resume logic, and make any object work with loops like for...of. Master yield for emitting values, yield* for delegating to other iterables, and next()/return()/throw() for driving and controlling the flow — and you have a tool that powers everything from custom iterables to the async/await syntax built on top of it.