Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 10 – Iterators and generators

Two-way generators: next, return, throw, yield*

Send values into JavaScript generators with next(value), finish them with return() and throw(), clean up in finally, and delegate with yield*.

  • Advanced
  • 22 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 22 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Send values into a generator with next(value) and predict where each one lands
  • Handle return() and throw() inside a generator and put cleanup in finally
  • Use yield* to delegate to other generators and iterables
  • Build a small state machine driven by events sent to a generator

Before you start

What you’ll learn · Before you start

What you will learn

  • Send values into a generator with next(value) and predict where each one lands
  • Handle return() and throw() inside a generator and put cleanup in finally
  • Use yield* to delegate to other generators and iterables
  • Build a small state machine driven by events sent to a generator

Before you start

On this page

A generator’s yield works in both directions. It sends a value out to the caller, and when the caller resumes the generator with next(value), the paused yield expression evaluates to that value inside the generator. The two other methods of a generator object resume it differently: return(value) makes the paused yield act like a return statement, so the generator finishes after running its finally blocks, and throw(error) makes it act like a throw statement, so the generator can catch the error or let it out. yield* hands the whole conversation to another generator, or walks any other iterable. Together these turn a generator from a value factory into a small process that a caller can talk to: a running total, a parser that can put a token back, or a state machine driven by events. This lesson traces where every value lands, reproduces the cleanup that never happens, and builds a crossing light.

Sending values in with next(value)

This generator keeps a running total of the amounts it is sent. Read const amount = yield total as “hand out the total, then wait; whatever the next next() brings is the amount”:

A till that adds what it is sent JavaScript · running-total.mjs
// next(value) resumes the generator, and the paused yield evaluates to that value.
function* till() {
  let total = 0;
  console.log('  till opened');
  for (;;) {
    const amount = yield total; // hands out the total, then waits for the next amount
    total += amount;
    console.log(`  received ${amount}`);
  }
}

const register = till();
console.log(register.next('ignored')); // starts the body: no yield is waiting for this value
console.log(register.next(250));
console.log(register.next(120));
console.log(register.next(30));

Output

  till opened
{ value: 0, done: false }
  received 250
{ value: 250, done: false }
  received 120
{ value: 370, done: false }
  received 30
{ value: 400, done: false }

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node running-total.mjs

Diagram: three calls of next() on till(); the first starts the body and loses its value, each later value becomes the amount.next('ignored')starts the body; no yieldwas waiting: value lost→ { value: 0 }next(250)the yield gives 250:total = 250→ { value: 250 }next(120)the yield gives 120:total = 370→ { value: 370 }

Where the value of each next() lands

Text description of the diagram

The diagram has three rows, one per call in running-total.mjs: the call on the left, what it does on the right.

  1. `next('ignored')` starts the body, which runs to its first `yield total`. No `yield` was waiting, so `'ignored'` is lost. The answer is `{ value: 0 }`.
  2. `next(250)`: the paused `yield total` evaluates to 250, so `amount` becomes 250 and the total 250. The generator pauses at the next `yield total` and answers `{ value: 250 }`.
  3. `next(120)`: `amount` becomes 120 and the total 370, and the answer is `{ value: 370 }`.

Each value goes in with one call, and its effect comes out in the answer to that same call.

The first call, next('ignored'), started the body: it printed “till opened” and ran to the first yield total, answering with 0. Its argument was lost, because no yield was paused yet to receive it. That is why code that sends values into a generator usually makes one empty next() call first, to bring the body to its first yield. Every later next(value) resumes the paused yield, which evaluates to the value, and the call returns whatever the generator yields next. So the answer to next(250) is already the new total, 250, and next(120) gives 370.

yield binds loosely, like an assignment: yield total + 1 yields total + 1. Inside a larger expression, put it in parentheses, as in (yield total) * 2.

Finishing from outside: return() and throw()

return(value) and throw(error) resume a paused generator just as next() does, but the paused yield behaves as if a return or a throw statement stood in its place:

return() and throw() on a paused generator JavaScript · return-throw.mjs
// return() and throw() resume a paused generator too: one finishes it, the other raises an error at the yield.
function* steps() {
  try {
    yield 'step 1';
    yield 'step 2';
    yield 'step 3';
  } finally {
    console.log('  finally runs');
  }
}

const a = steps();
console.log(a.next());
console.log(a.return('stopped')); // the finally block runs first
console.log(a.next());

const b = steps();
b.next();
try {
  b.throw(new Error('disk full')); // nothing inside catches it, so it comes back out
} catch (error) {
  console.log('caller caught:', error.message);
}
console.log(b.next());

// A generator that catches the error decides what happens next.
function* retries() {
  for (let attempt = 1; attempt <= 3; attempt++) {
    try {
      yield `attempt ${attempt}`;
    } catch (error) {
      console.log(`  attempt ${attempt} failed: ${error.message}`);
    }
  }
}
const c = retries();
console.log(c.next().value);
console.log(c.throw(new Error('timeout')).value); // caught inside: the reply is the next yield
console.log(c.next());

// Before the first next(), the body has not started, so there is no try block to run.
console.log(steps().return('never started'));
try {
  steps().throw(new Error('too early'));
} catch (error) {
  console.log('thrown straight back:', error.message);
}

Output

{ value: 'step 1', done: false }
  finally runs
{ value: 'stopped', done: true }
{ value: undefined, done: true }
  finally runs
caller caught: disk full
{ value: undefined, done: true }
attempt 1
  attempt 1 failed: timeout
attempt 2
{ value: 'attempt 3', done: false }
{ value: 'never started', done: true }
thrown straight back: too early

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node return-throw.mjs

  • a.return('stopped') finished the generator at its first yield. The finally block ran first, and the call returned { value: 'stopped', done: true }. After that, next() only ever answers done: true.
  • b.throw(new Error('disk full')) raised the error at the paused yield. Nothing inside caught it, so the finally block ran, the error came back out of the throw() call to the caller, and the generator was finished.
  • retries catches the error around its yield, so the generator decides what happens next. Its loop continues, and the throw() call returns the next yielded value, attempt 2. A caller can use this to tell a generator that the last value it handed out failed.
  • Before the first next(), the body has not started, so there is no try block to run. return() finishes the generator at once, and throw() throws the error straight back to the caller.

On a generator that has already finished, return(value) returns { value, done: true } and throw(error) throws the error.

Cleanup belongs in finally

A generator that opens something, such as a file, a database cursor or a lock, must close it whether the consumer reads to the end or stops early. A finally block does that, because return() runs it:

When finally runs, and when it does not JavaScript · cleanup.mjs
// Cleanup in finally runs whenever the consumer stops early, as long as someone calls return().
function* readRows(file) {
  console.log(`  open ${file}`);
  try {
    for (let row = 1; row <= 1000; row++) yield `${file} row ${row}`;
  } finally {
    console.log(`  close ${file}`);
  }
}

for (const row of readRows('orders.csv')) {
  console.log(row);
  if (row.endsWith('row 2')) break; // break calls return(), which runs finally
}

const [header] = readRows('users.csv'); // destructuring closes it too
console.log(header);

try {
  for (const row of readRows('refunds.csv')) throw new Error(`cannot parse ${row}`);
} catch (error) {
  console.log('caught:', error.message);
}

const abandoned = readRows('audit.csv');
console.log(abandoned.next().value);
console.log('the program moves on: audit.csv is never closed');

Output

  open orders.csv
orders.csv row 1
orders.csv row 2
  close orders.csv
  open users.csv
  close users.csv
users.csv row 1
  open refunds.csv
  close refunds.csv
caught: cannot parse refunds.csv row 1
  open audit.csv
audit.csv row 1
the program moves on: audit.csv is never closed

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node cleanup.mjs

Three consumers stopped early, and all three closed their file. The orders.csv loop broke after two rows, and for…of called return(). The destructuring of users.csv took one row and closed the generator before printing it, which is why “close users.csv” appears before the header. The refunds.csv loop was left by an exception, and for…of closed the iterator before the error reached the catch.

The fourth generator was never closed. The program took one row from audit.csv with next() and moved on, and nothing calls return() for it later: a generator that becomes garbage simply disappears, and its finally block never runs. Whoever drives a generator by hand owns its cleanup. Call return() when you stop, ideally from your own finally, or consume the generator with for…of, which does it for you.

Two rules keep finally blocks honest. Never yield inside one: return() would then stop at that yield and answer done: false instead of finishing. And keep cleanup short and safe, because it also runs while an error from the loop body is on its way out.

A state machine driven by events

A generator remembers where it paused, so the position in its code can be the state of a machine, and each next(event) is a transition. This pedestrian crossing turns green, then waits for its button:

A crossing light that reacts to events JavaScript · traffic-light.mjs
// A pedestrian crossing as a generator: the light's state is the place where the generator is paused.
function* crossingLight() {
  for (;;) {
    try {
      let event = yield 'green';
      while (event !== 'walk') event = yield 'green'; // cars keep going until someone presses the button
      yield 'amber';
      yield 'red: cross now';
      yield 'red: cross now';
    } catch (fault) {
      let event = yield `flashing amber (${fault.message})`;
      while (event !== 'repaired') event = yield 'flashing amber';
    }
  }
}

const light = crossingLight();
const show = (label, result) => console.log(`${label.padEnd(9)} → ${result.value}`);

show('start', light.next());
for (const event of ['tick', 'walk', 'walk', 'tick', 'tick']) show(event, light.next(event));
show('fault', light.throw(new Error('lamp failed')));
show('tick', light.next('tick'));
show('repaired', light.next('repaired'));
show('tick', light.next('tick'));

Output

start     → green
tick      → green
walk      → amber
walk      → red: cross now
tick      → red: cross now
tick      → green
fault     → flashing amber (lamp failed)
tick      → flashing amber
repaired  → green
tick      → green

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node traffic-light.mjs

Read the generator from top to bottom and it describes the light’s behaviour as a story. It stays green, whatever ticks arrive, until a walk event; then it shows amber for one event and red for two, and the loop starts again. The second walk arrived during amber and changed nothing, because no code at that point looks at the event. A fault is sent with throw(): the error lands at whichever yield the light is paused at, the catch block switches to flashing amber, and only a repaired event brings it back to green.

Compared with a state variable and a switch over it, the generator version keeps sequences readable and needs no transition table. Its weakness is that its state is invisible: a paused generator cannot be inspected, saved to storage or sent over the network, and a page reload loses it. When a machine’s state must be shown, stored or restored, keep it in a plain variable.

Delegating with yield*

yield* iterable yields every value of another iterable, and its own value is that iterable’s return value. With a generator on the other side it is more than a loop: while the inner generator runs, every next(value), throw() and return() of the caller goes straight to it.

Values in and out through yield* JavaScript · yield-star.mjs
// yield* hands the conversation to another generator, or walks any other iterable.
function* inner() {
  const reply = yield 'inner asks';
  console.log(`  inner got: ${reply}`);
  return 'inner result';
}

function* outer() {
  yield 'outer starts';
  const result = yield* inner(); // next(value), throw() and return() go to inner while it runs
  console.log(`  yield* evaluated to: ${result}`);
  yield* ['a', 'b'];
  yield* 'hi';
}

const g = outer();
console.log(g.next().value);
console.log(g.next().value);
console.log(g.next('hello from outside').value);
console.log([...g]);

Output

outer starts
inner asks
  inner got: hello from outside
  yield* evaluated to: inner result
a
[ 'b', 'h', 'i' ]

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node yield-star.mjs

The value sent with next('hello from outside') reached inner, two levels down, without outer doing anything, and inner’s return 'inner result' became the value of the yield* expression in outer. Then yield* walked an array and a string like any for…of loop would.

Delegation makes recursive data easy to walk lazily, because a generator can delegate to another call of itself:

Walking a folder tree with recursive yield* JavaScript · folder-tree.mjs
// A recursive generator: yield* walks each subfolder with the same function.
const project = {
  name: 'shop',
  children: [
    { name: 'src', children: [{ name: 'cart.mjs' }, { name: 'checkout.mjs' }] },
    { name: 'tests', children: [{ name: 'cart.test.mjs' }, { name: 'checkout.test.mjs' }] },
    { name: 'package.json' },
  ],
};

function* outline(node, depth = 0) {
  yield `${'  '.repeat(depth)}${node.children ? `${node.name}/` : node.name}`;
  for (const child of node.children ?? []) yield* outline(child, depth + 1);
}

function* files(node, path = '') {
  const here = `${path}${node.name}`;
  if (!node.children) yield here;
  else for (const child of node.children) yield* files(child, `${here}/`);
}

for (const line of outline(project)) console.log(line);

let visited = 0;
for (const file of files(project)) {
  visited += 1;
  if (file.endsWith('.test.mjs')) {
    console.log(`first test file: ${file}, found after ${visited} files`);
    break; // closes every generator in the chain of yield*
  }
}

Output

shop/
  src/
    cart.mjs
    checkout.mjs
  tests/
    cart.test.mjs
    checkout.test.mjs
  package.json
first test file: shop/tests/cart.test.mjs, found after 3 files

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node folder-tree.mjs

outline yields one line per folder or file, indented by depth, and yield* outline(child, depth + 1) lets each subfolder produce its own lines. files walks the same tree for file paths only. The search for the first test file stopped after three files, and its break closed every generator in the chain, from the deepest yield* up to the outermost call, so nothing walked the rest of the tree.

Folder Tree Generator Draw a real project's folder tree as text, then write an outline generator that prints the same lines from a nested object.

Generators and async/await

Before JavaScript had async functions, libraries used generators for asynchronous code. The generator yielded a promise, and a small runner waited for it and resumed the generator with next(result), or with throw(error) when the promise was rejected. This runner is about 20 lines:

A generator runner against async and await JavaScript · mini-co.mjs
// How generators once stood in for async functions: a runner resumes the generator with each promise's result.
function run(generatorFunction) {
  return new Promise((resolve, reject) => {
    const gen = generatorFunction();
    function step(method, input) {
      let result;
      try {
        result = gen[method](input); // next(value) or throw(error)
      } catch (error) {
        reject(error);
        return;
      }
      if (result.done) resolve(result.value);
      else Promise.resolve(result.value).then((value) => step('next', value), (error) => step('throw', error));
    }
    step('next', undefined);
  });
}

const findUser = (id) => (id > 0 ? Promise.resolve({ id, name: `user ${id}` }) : Promise.reject(new Error(`no user ${id}`)));

const fromGenerator = await run(function* () {
  const user = yield findUser(7);
  console.log('generator got', user.name);
  try {
    yield findUser(-1);
  } catch (error) {
    console.log('generator caught:', error.message);
  }
  return 'generator done';
});
console.log(fromGenerator);

// The same steps with async and await, which the language has had since ES2017.
async function lookUp() {
  const user = await findUser(7);
  console.log('async got', user.name);
  try {
    await findUser(-1);
  } catch (error) {
    console.log('async caught:', error.message);
  }
  return 'async done';
}
console.log(await lookUp());

Output

generator got user 7
generator caught: no user -1
generator done
async got user 7
async caught: no user -1
async done

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node mini-co.mjs

The generator and the async function print the same steps: the yield and the await both pause until the promise settles, a rejection arrives as an exception at that point and can be caught, and the final return becomes the result. Async functions arrived in ES2017 and replaced this use of generators: they need no runner, await works in any expression, and engines and debuggers know what they are. Generators kept their main job, lazy sequences and iterables, and the two ideas meet again in async function*, the async generators of the module on asynchronous JavaScript.

Exercises

Exercise · Hard · JavaScript

Tokenize arithmetic with a generator that can put a token back

Write tokenize(source) in tokens.mjs: a generator that reads an arithmetic expression such as '12 + 3*(4-1)' and yields its tokens one at a time, from left to right.

  • A number is one or more digits with an optional decimal part, such as 12, 0.5 or 3.25. Yield it as { type: 'number', value: 12 }, with value a JavaScript number.
  • Each of + - * / is { type: 'operator', value: '+' }, and each parenthesis is { type: 'paren', value: '(' }.
  • Spaces and tabs between tokens are skipped.
  • Any other character is a SyntaxError, and so is a number followed directly by a dot, such as 1.2.3 or 5.: reject the whole number, without yielding its first part. Throw the error when the tokenizer reaches it, so the tokens before it are still delivered first.

A parser often needs to look at the next token and leave it for later. Support that through the value of next(): when the caller calls next('back'), the generator yields the token it has just handed out again, instead of moving on. next('back') may be called several times in a row; any other call of next() moves on to the following token.

Starter code · tokens.mjs

/**
 * @typedef {{ type: 'number', value: number } | { type: 'operator' | 'paren', value: string }} Token
 */

/**
 * The tokens of an arithmetic expression, from left to right. After next('back'), the same token comes again.
 * @param {string} source
 * @returns {Generator<Token, void, string | undefined>}
 */
export function* tokenize(source) {}
The sample tests · tokens.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { tokenize } from './tokens.mjs';

const num = (value) => ({ type: 'number', value });
const op = (value) => ({ type: 'operator', value });
const paren = (value) => ({ type: 'paren', value });

test('an expression becomes numbers, operators and parentheses', () => {
  assert.deepEqual([...tokenize('12 + 3*(4-1)')], [num(12), op('+'), num(3), op('*'), paren('('), num(4), op('-'), num(1), paren(')')]);
});

test('decimals, tabs and extra spaces', () => {
  assert.deepEqual([...tokenize('  0.5\t* 10 / 2.25 ')], [num(0.5), op('*'), num(10), op('/'), num(2.25)]);
});

test('an empty expression has no tokens', () => {
  assert.deepEqual([...tokenize('')], []);
  assert.deepEqual([...tokenize('   ')], []);
});

test("next('back') gives the same token again", () => {
  const tokens = tokenize('7 - 2');
  assert.deepEqual(tokens.next().value, num(7));
  assert.deepEqual(tokens.next('back').value, num(7));
  assert.deepEqual(tokens.next('back').value, num(7));
  assert.deepEqual(tokens.next().value, op('-'));
  assert.deepEqual(tokens.next().value, num(2));
  assert.equal(tokens.next().done, true);
});

test('an unknown character is a SyntaxError, after the tokens before it', () => {
  const tokens = tokenize('2 + x');
  assert.deepEqual(tokens.next().value, num(2));
  assert.deepEqual(tokens.next().value, op('+'));
  assert.throws(() => tokens.next(), SyntaxError);
});

test('a malformed number is a SyntaxError, without its first part', () => {
  const tokens = tokenize('4 * 1.2.3');
  assert.deepEqual(tokens.next().value, num(4));
  assert.deepEqual(tokens.next().value, op('*'));
  assert.throws(() => tokens.next(), SyntaxError);
  assert.throws(() => tokenize('5. + 1').next(), SyntaxError);
});
A hint

Keep a position i and loop while i < source.length. To read a number, try a sticky regular expression at i, such as /\d+(?:\.\d+)?/y with lastIndex = i, and check that the character after the match is not a digit or a dot. For the put-back protocol, remember what each yield returns: let reply = yield token; and then while (reply === 'back') reply = yield token; before moving on.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 What does running-total.mjs print?

    What does this program print? Choose one answer.

    // next(value) resumes the generator, and the paused yield evaluates to that value.
    function* till() {
      let total = 0;
      console.log('  till opened');
      for (;;) {
        const amount = yield total; // hands out the total, then waits for the next amount
        total += amount;
        console.log(`  received ${amount}`);
      }
    }
    
    const register = till();
    console.log(register.next('ignored')); // starts the body: no yield is waiting for this value
    console.log(register.next(250));
    console.log(register.next(120));
    console.log(register.next(30));
    Show the answer to question 1

    Answer: it prints

      till opened
    { value: 0, done: false }
      received 250
    { value: 250, done: false }
      received 120
    { value: 370, done: false }
      received 30
    { value: 400, done: false }

    The first next() starts the body, which prints "till opened" and pauses at its first yield, so 'ignored' has no yield to land in. Each later next(amount) makes the paused yield evaluate to the amount, and the same call returns the total yielded after adding it.

  2. Question 2 of 5 const g = gen(); What happens to the 5 in g.next(5) when it is the first call of next()?

    Choose one answer.

    Show the answer to question 2

    Answer: It is ignored, because no yield is paused yet to receive it

    A value sent with next() is the result of the yield where the generator is paused. Before the first call the body has not started, so there is no such yield, and the value is dropped.

  3. Question 3 of 5 What does this print?

    Read the code, then choose one answer.

    function* inner() {
      yield 1;
      return 2;
    }
    function* outer() {
      const r = yield* inner();
      yield r * 10;
    }
    console.log([...outer()]);
    Show the answer to question 3

    Answer: [ 1, 20 ]

    yield* passes on the values inner yields, here 1, and evaluates to inner's return value, 2, which is not one of the yielded values. outer then yields 2 × 10.

  4. Question 4 of 5 A generator opens a file and closes it in finally. Which use of it leaves the file open?

    Choose one answer.

    Show the answer to question 4

    Answer: const g = rows(); g.next(); followed by no further calls on g

    for…of and destructuring call return() when they stop early, and return() runs finally, even when the loop is left by an exception. A generator that someone started with next() and then forgot is never resumed, and garbage collection does not run its finally block.

  5. Question 5 of 5 A generator catches errors around its yield and then continues its loop to the next yield. What does g.throw(new Error('x')) return?

    Choose one answer.

    Show the answer to question 5

    Answer: The result of the next yield, such as { value: 'attempt 2', done: false }

    throw() raises the error at the paused yield. When the generator catches it and runs on to another yield, the generator is still alive and throw() returns that yield's result. Only an uncaught error finishes it.

Interview questions

2 to 5 years · What does the value passed to next() do in a generator?

next(value) resumes a paused generator, and the yield expression where it paused evaluates to value. So in const amount = yield total, the generator hands out total and, when the caller next calls next(250), amount becomes 250 and the body continues until the next yield, whose value becomes the result of that same next(250) call. The first next() call only starts the body, so its argument is ignored: no yield is waiting yet. That is why code that feeds values into a generator usually calls next() once with no argument first. return(value) and throw(error) resume the same paused yield but make it behave like a return or a throw statement, which is how a caller finishes a generator or reports a failure into it.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A generator reads rows from a database cursor and closes the cursor in a finally block. Under load, the database still reports cursors left open by your service. List the ways a consumer can leave this generator without its finally block running, and fix each one.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    Explain how a short run() function can make a generator that yields promises behave like an async function. Then say what async functions give you that such a runner does not, and when a design based on generators is still the better choice.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • next(value) resumes a generator, and the paused yield evaluates to value; the first next() only starts the body, so its argument is lost.
  • return(value) finishes a paused generator after running its finally blocks; throw(error) raises the error at the paused yield, where the generator may catch it and carry on.
  • for…of and destructuring call return() when they stop early, so cleanup in finally runs; a generator driven by hand and then abandoned is never cleaned up.
  • Never yield inside finally.
  • yield* forwards next, throw and return to the inner generator and evaluates to its return value; recursive yield* walks trees lazily.
  • A generator makes a readable state machine whose state is the position in its code, but that state cannot be inspected or saved.
  • async and await replaced generator runners for asynchronous code; generators remain the tool for lazy sequences.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.