JAVASCRIPT / DESTRUCTURING, ITERABLES, AND GENERATORS
Passing values back into generators with next
Feed values into a running generator with next(value), read yield as an expression, and know why the first next() argument is always discarded.
What you will learn
- Read `const x = yield v` as: send v out, pause, resume as next()'s argument
- Prime an interactive generator with one bare next() before sending real values
- Explain why the argument to the very first next() call is always discarded
- Recognise that for...of, spread and destructuring always call next() with no value
Understanding Passing values back into generators with next
In JavaScript `yield` is an operator that produces a value, not just a statement that emits one. When execution reaches `const amount = yield total`, the generator hands `total` out to the caller and freezes in the middle of that assignment, before the variable has been given anything. The next call to `next(v)` substitutes `v` for the whole `yield total` expression and lets the line finish, so `amount` becomes `v`. That is the entire mechanism: a generator receives data by having its suspended `yield` expressions replaced with the arguments of later `next` calls.
This creates an off-by-one that trips up almost everyone. The first `next()` call does not resume a paused `yield`, it starts the function body running from the top, so there is no suspended expression for its argument to land in and the argument is silently thrown away. Every following call resumes the `yield` that paused during the previous call, which means the value you pass on call number three answers the question asked by call number two. The usual fix is to prime the generator with one bare `next()` whose result you use as the first prompt, and only then start sending values.
Once a generator reads from `yield`, it is a coroutine rather than a plain data source, and that changes who is allowed to drive it. Only code that calls `next` by hand can send anything; `for...of`, spread, and array destructuring call `next()` with no argument, so every `yield` expression inside evaluates to `undefined`. Arithmetic then quietly becomes `NaN` and loop conditions flip, so the generator finishes early or spins forever without any error being thrown. Decide up front whether a generator produces values or consumes them, and drive consuming generators from an explicit loop.
function* runningTotal() {
let total = 0;
while (true) {
const amount = yield total; // send total out, wait for a number back
if (amount === undefined) return total;
total += amount;
}
}
const t = runningTotal();
console.log(t.next(100)); // 100 is dropped: nothing is paused yet
console.log(t.next(5)); // 5 becomes the value of the first yield
console.log(t.next(3));
console.log(t.next()); // undefined arrives, so the generator returns
A yield expression evaluates to whatever the next() call that resumes it passes in, which makes next(value) a channel back into a paused generator.
Worked examples
yield as a question
Shows how each next() argument answers the yield that paused on the previous call.
function* interview() {
const name = yield 'What is your name?';
const lang = yield `Hi ${name}, favourite language?`;
return `${name} likes ${lang}`;
}
const it = interview();
console.log(it.next().value);
console.log(it.next('Ada').value);
console.log(it.next('JavaScript').value);
Example explained
Line 1The bare `it.next()` runs the body only as far as the first `yield`, so it can return a prompt but cannot accept an answer.
Line 2`it.next('Ada')` replaces the first `yield` expression with 'Ada', so `name` is finally assigned and the template string for the second prompt is built.
Line 3`it.next('JavaScript')` fills in `lang` and lets the function reach `return`, so `value` is the summary string and `done` is true.
Line 4The answers lag the prompts by one call: three calls are needed to fill two variables.
Sent values steering control flow
Demonstrates that an incoming value can choose a branch, not just supply data.
function* collector() {
let items = [];
while (true) {
const command = yield items.length;
if (command === 'reset') items = [];
else items.push(command);
}
}
const c = collector();
c.next(); // prime
console.log(c.next('a').value);
console.log(c.next('b').value);
console.log(c.next('reset').value);
console.log(c.next('c').value);
Example explained
Line 1`c.next()` runs to the first `yield` and reports length 0; its result is ignored here because it only exists to prime the generator.
Line 2`const command = yield items.length` both reports the current size and receives the next instruction, so one yield serves as output and input.
Line 3`c.next('reset')` makes the `if` branch run instead of the push, so the same call reports 0 on the way back out.
Line 4The generator keeps `items` alive between calls, which is why the count resumes at 1 after the reset.
for...of sends nothing
Shows the silent breakage when an input-hungry generator is driven by for...of.
function* stepper() {
const step = yield 'pick a step size';
let n = 0;
while (n < 4) {
yield n;
n += step;
}
}
for (const v of stepper()) {
console.log(v);
}
Example explained
Line 1`for...of` calls `next()` with no arguments, so `step` is assigned `undefined` rather than a number.
Line 2`n += step` computes `0 + undefined`, which is `NaN`, and no error is raised.
Line 3`NaN < 4` is false, so the loop condition fails, the generator returns, and `for...of` stops after printing 0.
Line 4The generator is not broken in isolation; it is broken by a consumer that cannot answer its `yield`.
Important notes
`next(v)` reaches the innermost suspended `yield`. If the generator is currently inside a `yield*` delegation, the value is forwarded to the delegate's `yield`, not to the outer function.
`next` is only one of three ways to resume a paused generator: `gen.return(v)` makes the paused `yield` behave like a `return` statement (still running `finally` blocks), and `gen.throw(err)` makes it behave like a `throw` at that exact line.
Common mistakes
Sending the first real value on the very first `next(x)` call. Nothing is suspended yet, so `x` is discarded and every later answer lands in the wrong variable, producing totals and prompts that are off by one position.
Treating `yield n` as if it mutated `n`. Only the value of the `yield` expression itself is replaced by the incoming value; the yielded variable is untouched, so code that reads `n` afterwards still sees the old number.
Writing `const x = yield count + 1` when you meant to add 1 to the incoming value. That parses as `yield (count + 1)`, so the wrong number goes out and `x` is the raw sent value; you need `const x = (yield count) + 1`.
Try it yourself
Change, predict, then run
Write a generator `guessGame(secret)` that yields 'higher', 'lower', or 'correct' and receives each guess through `next()`. Prime it with a bare `next()`, then log the result of three `next(guess)` calls and confirm the first guess you pass is the one that gets judged.
Open the JavaScript workspaceCheck your understanding
Given `function* g() { const a = yield 1; const b = yield a + 1; return a + b; }`, what does the third call return in `const it = g(); it.next(10); it.next(20); it.next(30);`?
- { value: 30, done: true }
- { value: 50, done: true }
- { value: 21, done: false }
- { value: 60, done: true }
Show answer
The 10 is discarded because the first `next` only starts the body and no `yield` is suspended to receive it, so `a` is 20 and `b` is 30, and the function returns 50 with `done: true`. `{ value: 30, done: true }` is what you would get if `next(10)` filled in `a` and `next(20)` filled in `b`, which would require a `yield` to already be waiting before the body ever ran.