completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Generators

Learn JavaScript generator functions: how function* and yield pause work, why generators are iterators, how yield* delegates, how return and throw finish or interrupt, and how infinite lazy sequences stay safe.

By the end you can
  • 01
    Pause and go onSay exactly when a generator's code runs, and what each yield hands out.
  • 02
    Use generators as iteratorsLoop over a generator object with for...of and spread, and know it works only once.
  • 03
    Stop, interrupt, and combine generatorsUse return(), throw(), yield*, and endless generators without surprises.

Functions that can pause

A normal function runs from start to end in one go. You call it, and it keeps running until it returns a value or throws an error. A generator function is different. It can stop in the middle, hand you a value, and wait. Later you tell it to go on, and it carries on from the exact place where it stopped.

The keyword that hands out a value and pauses is yield. The method that says "go on" is next(). While the function waits, it keeps all its local variables, so nothing is lost.

Generators build on the last lesson, the iteration protocols. There you wrote a next() method by hand, and it gave back objects like { value, done }. A generator makes that next() method for you. You write normal code with loops and yield, and JavaScript makes the results. The object you get also works with for...of, spread (...), and destructuring.

The main idea

A generator function is written with function*: the word function with a star. Calling it does not run its code. It gives you a generator object. Each time you call the object's next() method, the code runs until the next yield, hands out that value, and pauses. The object also has return() and throw() methods, which you'll meet later in this lesson.

Real-life analogyA storyteller who pauses

Think of a storyteller who stops at the end of every scene. When you say "go on", they don't start the story again. They remember the characters and the exact sentence where they stopped, and they carry on from there.

In real life: The storyteller starts the tale
In JavaScript: The first next() starts the function's code
In real life: They stop at the end of a scene
In JavaScript: Each yield hands out a value and pauses
In real life: They remember exactly where they stopped
In JavaScript: The generator keeps its variables and its place
In real life: You say "go on"
In JavaScript: Calling next() again carries on after that yield

In this lesson you will:

  • Step through a generator, one line at a time.
  • Send commands to a live generator with buttons.
  • Use yield* to hand out the values of another array or generator.
  • Safely take a few values from a list that never ends.

function* and yield

STEP THROUGH

To write a generator, put a star after the word function, like this: function*. Inside it, use yield to hand out a value.

Here is the surprising part: calling a generator function runs none of its code. It only gives you a generator object. The code starts on the first next() call, and it runs until the first yield.

The three cards below show the three steps. They use the story example that you'll step through soon.

1. Createconst tale = story();

No line inside story has run yet.

2. Runtale.next()

The code runs until the next yield, a return, or an error.

3. Pause{ value, done: false }

The function waits here. Its variables are kept.

Calling a generator runs none of its codePop out in the code editor (opens in a new tab)JavaScript
function* makeTea() {  console.log("body ran");  yield "tea";} const cup = makeTea();console.log("created");

Lines 1 to 4 write a generator called makeTea. Line 2 would print body ran. Line 6 calls makeTea() and keeps the generator object in cup. But line 2 does not run, because nobody called next(). Line 7 prints created, and that is the only output.

This is useful when the work inside should wait until someone really asks for a value.

Each next() gives back a result objectPop out in the code editor (opens in a new tab)JavaScript
function* colors() {  yield "red";  yield "blue";} const list = colors();console.log(JSON.stringify(list.next()));console.log(JSON.stringify(list.next()));console.log(JSON.stringify(list.next()));

Lines 7, 8, and 9 call list.next() three times. JSON.stringify turns each result into text, so you can see its two properties:

  • value is the value handed out by yield.
  • done is false while the generator is paused at a yield, and true once its code has finished.

The first call prints {"value":"red","done":false}. The second prints {"value":"blue","done":false}. The third call finds no more yield, so it prints {"done":true}. Its value is undefined, and JSON.stringify leaves out properties that are undefined.

Three list.next() calls on colors(). The 1st gives red with done false and pauses at line 2. The 2nd gives blue with done false and pauses at line 3. The 3rd gives undefined with done true, because the code is finished.1st next()"red"done: falsepaused at line 22nd next()"blue"done: falsepaused at line 33rd next()undefineddone: truefinished
Each next() runs the code up to the next yield and pauses there. The third call finds no more yield, so the result says done: true.

When you call next() yourself, check done to know when to stop.

Next, step through a longer example called story. Each step shows one line running. The steps were recorded from real JavaScript, but this is not a view inside the JavaScript engine. The "Running in" row shows where the code is right now: in the main script, or inside the generator.

You can also change the last command, on line 20, to see three endings: go on as normal, finish early, or throw an error into the generator.

Step through: pause, go on, and clean up
Step 0 of 10Ready
Your turn: follow the blue line

Guess first: what runs when story() is called? Step through to see that none of its code runs until next() starts it.

Running in
  1. script
Next: line 17
function* story() {  console.log("story starts");  try {    yield "chapter 1";    console.log("after first pause");    yield "chapter 2";    console.log("after second pause");    return "the end";  } catch (error) {    console.log("caught: " + error.message);    return "recovered";  } finally {    console.log("cleanup");  }} console.log("created");tale.next();tale.next();
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Change the last command on line 20

Changing the command starts a new recording. Watch the Running in row switch between the script and the paused generator.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Two more details are worth knowing.

First, next() can take a value, like next("hi"). That value becomes the result of the paused yield. But on the first call, no yield has paused yet, so the value has nowhere to go. JavaScript ignores it. Sending values into a generator gets its own lesson later: Generators for control flow.

Second, a generator's local variables are not shared with the rest of the program. They belong to that one paused generator object. It's like the local variables of a normal function call, which you saw in the Execution contexts lesson.

The first next(value) is ignoredPop out in the code editor (opens in a new tab)JavaScript
function* reply() {  const answer = yield "ready";  console.log("answer: " + answer);} const task = reply();console.log(JSON.stringify(task.next("ignored")));console.log(JSON.stringify(task.next("accepted")));

Line 2 says const answer = yield "ready". The yield hands out ready and pauses. When the generator goes on, the value sent with next() is stored in answer.

Line 7 calls next("ignored"). The code runs to the yield, and line 7 prints {"value":"ready","done":false}. The word ignored is dropped, because no yield was paused yet.

Line 8 calls next("accepted"). Now the paused yield receives accepted, so line 3 prints answer: accepted. Then the code ends, and line 8 prints {"done":true}.

Generators as iterators

INTERACTIVE

A generator object is both an iterator and an iterable. You met both words in the last lesson:

  • An iterator has a next() method that gives one result at a time.
  • An iterable has a [Symbol.iterator]() method, so for...of and spread can loop over it.

A generator object has both methods. Its [Symbol.iterator]() method simply gives back the object itself. So for a generator object called it, it[Symbol.iterator]() === it is true.

The table below compares a generator object with an array. Read each row as a question, then the answer for each one.

How a generator object differs from an array
QuestionGenerator objectArray
When are the values made?One at a time, only when someone asks for the next one.Usually all at once. They are already stored.
Can it go on forever?Yes, as long as the code that asks for values stops at some point.No. An array always has a fixed number of items.
Can you loop over it twice?No. Once it is used up, the same object stays finished.Yes. An array keeps its items.
What can you do with it?Call next(), or loop over it with for...of and spread.Loop over it, and also use array methods like map.
A small generator used as an iteratorPop out in the code editor (opens in a new tab)JavaScript
function* colors() {  yield "red";  yield "blue";} const list = colors();console.log(list.next());       // { value: "red", done: false }console.log([...colors()]);     // ["red", "blue"]console.log([...list]);         // ["blue"] because red was already consumed

Line 6 makes a generator object called list. Line 7 calls list.next() once and gets red. Line 8 spreads a brand-new generator object from colors(), so it gets both colors. Line 9 spreads list, the object from line 6.

Line 9 surprises many people. It prints only ["blue"]. list already gave out red on line 7, so the spread starts from where list paused, not from the beginning. To start again, call the generator function again to get a fresh object.

A generator object works only oncePop out in the code editor (opens in a new tab)JavaScript
function* chores() {  yield "wash";  yield "read";} const today = chores();console.log([...today].join(", "));console.log([...today].join(", ") || "nothing left");

Line 6 makes one generator object, today. Line 7 spreads it and prints wash, read. That uses it up. Line 8 spreads the same object again. It has no values left, so join gives an empty string, and the || "nothing left" part prints nothing left instead.

If some code may need the values again, keep the generator function and call it again for a fresh object.

In the playground below, you send commands to one live generator. Each button calls a method on the same object, so the order you press them in matters. Reset makes a new object. You'll learn about return() and throw() in the next section, but you can try them now.

Send commands to one live generator
The generator the buttons talk toPop out in the code editor (opens in a new tab)JavaScript
function* task() {  console.log("start");  try {    yield "first";    console.log("after first");    yield "second";  } catch (error) {    console.log("caught " + error.message);  } finally {    console.log("cleanup");  }  return "done";}
What you got back, and what ran insidelive

Results you got back

No commands yet.

Printed inside the generator

Nothing printed yet.

Try it yourself

The generator is made, but none of its code has run. Press next() to start it.

One live generator object. Every button talks to the same object. Reset makes a new one.

next(), return(), and throw()

next() is the normal "go on" command. A generator object has two more methods. Both act on a generator that is paused at a yield:

  • return(value) tells the generator to finish now. It gives back { value, done: true }. If the generator paused inside a try that has a finally block, the finally code still runs first. A yield inside that finally block could even pause it once more before it finishes.
  • throw(error) throws the error at the paused yield, as if that line had thrown it. A try...catch around that yield can catch it. If the generator has not started yet, there is no paused yield, so the error is thrown straight back to the code that called throw().

Both methods work well with the try, catch & finally rules you already know. Put clean-up code, like closing a connection, in a finally block. Then it runs even when a loop stops early.

Arrow functions cannot be generators

Only the function* form makes a generator. There is no arrow function version. Also, you can't use new with a generator function: new story() throws a TypeError.

A final return value shows up only oncePop out in the code editor (opens in a new tab)JavaScript
function* makeReceipt() {  yield "item";  return "total";} const receipt = makeReceipt();console.log(JSON.stringify(receipt.next()));console.log(JSON.stringify(receipt.next()));console.log([...receipt]);

Line 2 yields item, and line 3 returns total. Line 7 prints the first result, with done: false. Line 8 prints total with done: true. Line 9 spreads what is left and prints an empty array, [].

for...of and spread only collect values from yield. They stop at done: true, so they never see a generator's final return value.

return() still runs the finally blockPop out in the code editor (opens in a new tab)JavaScript
function* openConnection() {  try {    yield "connected";  } finally {    console.log("closed");  }} const connection = openConnection();console.log(connection.next().value);console.log(JSON.stringify(connection.return("stopped")));

Line 10 calls next(). The code runs to the yield on line 3 and pauses inside the try. Line 10 prints connected.

Line 11 calls return("stopped"). The generator finishes from that paused yield, but first its finally block runs and prints closed. Then line 11 prints the result: {"value":"stopped","done":true}.

connection.return("stopped") is sent to a generator paused at its yield. First the finally block runs and prints closed. Then the result is done true with the value stopped.connection.return("stopped")pausedconnectionat the yieldfinallyprints "closed"done: truevalue: "stopped"
The generator is paused at its yield. return() wakes it up only to finish: the finally block prints closed, then the result is done: true.
throw() can be caught inside the generatorPop out in the code editor (opens in a new tab)JavaScript
function* safeTask() {  try {    yield "waiting";  } catch (error) {    yield "recovered: " + error.message;  }} const task = safeTask();console.log(task.next().value);console.log(task.throw(new Error("retry" )).value);

Line 10 calls next(), and the generator pauses at the yield on line 3. Line 10 prints waiting.

Line 11 calls throw() with an error whose message is retry. The error is thrown at the paused yield on line 3, so the catch on line 4 catches it. Line 5 yields recovered: retry, and line 11 prints that value.

yield* delegation

INTERACTIVE

yield* (yield with a star) means: "hand out every value from this other iterable, one at a time, and then carry on here." Passing the work to another iterable like this is called delegation. It works with any iterable: arrays, strings, sets, your own iterables, and other generators.

When the other iterable is a generator, there is one more thing. That generator's return value becomes the result of the yield* expression, so you can save it in a variable.

Real-life analogyyield* hands the microphone to a guest

A show host talks for a while, then hands the microphone to a guest. The host waits until the guest is done. Then the host takes the microphone back and carries on. The guest's last words are said only to the host, and the host can choose to repeat them to the audience.

In real life: The host starts the show
In JavaScript: The outer generator yields its own values
In real life: The guest takes the microphone
In JavaScript: yield* guest() hands out each of the guest's values
In real life: The guest's last words, said only to the host
In JavaScript: The other generator's return value
In real life: The host takes the microphone back
In JavaScript: The code after yield* runs
yield* can hand out an array's itemsPop out in the code editor (opens in a new tab)JavaScript
function* names() {  yield* ["Asha", "Ben"];  yield "done";} console.log([...names()].join(", "));

Line 2 uses yield* on the array ["Asha", "Ben"]. It hands out Asha, then Ben. Then line 3 yields done. So line 6 prints Asha, Ben, done.

The array holds "Asha" and "Ben". An arrow labelled yield* passes them out as Asha and Ben. Then names() yields done from line 3. The output order is Asha, Ben, done."Asha""Ben"arrayyield*what names() gives, in orderAshaBendonefrom the arrayfrom line 3
yield* passes out every item of the array, one at a time. When the array has no more items, names() goes on to line 3 and yields "done".

An array has no return value, so here the yield* expression just gives undefined.

The playground below has two examples:

  • Story team: a generator saves another generator's return value and then yields it.
  • Flatten arrays: yield* is used inside a function that calls itself.
Hand the microphone to another iterable
A host generator and a guest generatorPop out in the code editor (opens in a new tab)JavaScript
function* leaf(name) {  yield name + ": start";  yield name + ": end";  return name + " finished";} function* storyTeam() {  yield "host intro";  const finalLine = yield* leaf("guest");  yield "host heard " + finalLine;  return "host finished";} console.log([...storyTeam()].join(" | "));
Outputhost and guest
  • host intro
  • guest: start
  • guest: end
  • host heard guest finished
  • The last next() call gives back { value: "host finished", done: true }
Try it yourself

Line 9 uses yield* leaf("guest") to hand the microphone to the guest generator. When the guest returns, its last words are saved in finalLine, and line 10 yields them.

yield* works with any iterable. When the other iterable is a generator, yield* also gives back its return value.

The flatten example uses the same idea as the Recursion lesson. Each nested array gets its own flatten call. yield* passes the values from the deeper call out to the code that loops over the result.

Infinite sequences

INTERACTIVE

A generator makes each value only when someone asks for it. This is called being lazy. Because of this, a generator can describe an infinite sequence: a list of values that never ends. An array could never store one. Some examples: IDs, timestamps, retry attempts, Fibonacci numbers, pages from a long search, or events as they happen.

The rule is simple. An endless generator is safe only when the code that asks for values stops asking.

The take helper in the playground does that stopping. take(ids(), 3) asks ids() for three values, hands them out, and then stops.

take(ids(), 3). The endless ids() generator makes 1, 2, and 3, one at a time. take stops asking after 3, so 4, 5, 6 and the rest are never made. The output is 1, 2, 3.[...take(ids(), 3)]ids()never stops1234, 5, 6 …never madetake stops at 31, 2, 3
ids() could count forever, but it only makes a number when asked. Here take asks three times and then stops, so 4 is never made.
Take a few values from a list that never ends
An endless ID maker and a take helperPop out in the code editor (opens in a new tab)JavaScript
function* ids() {  let id = 1;  while (true) {    console.log("compute id " + id);    yield id;    id += 1;  }} function* take(iterable, limit) {  const iterator = iterable[Symbol.iterator]();  for (let count = 0; count < limit; count += 1) {    const result = iterator.next();    if (result.done) return;    yield result.value;  }} console.log([...take(ids(), 3)].join(", "));
IDsmade only when asked

Values: 1, 2, 3

Log:

  1. compute id 1
  2. compute id 2
  3. compute id 3
Try it yourself

take(ids(), 3) makes exactly three IDs.

This page never runs [...ids()] or [...fibonacci()] on purpose: they would never finish.
Never spread an endless generator

[...ids()], Array.from(ids()), and a for...of loop with no break all keep asking for values until they get done: true. An endless generator never gives done: true, so they never stop. Put a limit in front first, like take(ids(), 3).

Lazy, eager, or unsafe?
  • [...take(ids(), 3)]
  • orders.map(order => order.id)
  • [...ids()]
  • function* paidOrderIds(orders) { ... }
  • orders.filter(order => order.paid)
  • Array.from(fibonacci())
Try it yourself
0 of 6 correct

Put each line of code in its group. Lazy: values are made only when asked. Eager: the whole result is made right away. Unsafe: it asks an endless generator for every value.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you'll use generators

Generators are not for every loop. Use one when:

  • the values come one after another, over time,
  • building the whole array first would waste memory or work, or
  • a function that calls itself (recursion) needs to pass results back out as it finds them.
Make three order IDs from an endless ID makerPop out in the code editor (opens in a new tab)JavaScript
function* makeIds(start) {  let id = start;  while (true) {    yield "order-" + id;    id += 1;  }} function* take(iterable, limit) {  let count = 0;  for (const value of iterable) {    if (count === limit) return;    count += 1;    yield value;  }} console.log([...take(makeIds(100), 3)].join(", "));

Line 1 starts makeIds, a generator that makes IDs. Line 2 keeps the next number in a local variable, id. The loop on line 3 never ends. Each time round, line 4 yields one ID, and line 5 adds 1.

Line 18 uses take to keep only three IDs, so it prints order-100, order-101, order-102. This take is written with for...of. When a value arrives after the limit, line 12 returns. That ends the for...of loop, and the loop closes the endless ID maker.

Read search results one page at a timePop out in the code editor (opens in a new tab)JavaScript
function* pageResults(pages) {  for (const page of pages) {    console.log("request page");    yield* page;  }} function* take(iterable, limit) {  let count = 0;  for (const value of iterable) {    if (count === limit) return;    count += 1;    yield value;  }} const pages = [["Asha", "Ben"], ["Chen", "Di"]];console.log([...take(pageResults(pages), 3)].join(", "));

pageResults loops over the pages. Line 3 prints request page; it stands in for asking a server for the next page. Line 4 uses yield* to hand out that page's names one by one, without first joining all the pages into one big array.

Line 18 asks for three names. The first page gives Asha and Ben. Then the second page is requested, and Chen is the third name. So the output is two request page lines, then Asha, Ben, Chen. The name Di is never printed.

A traffic light that remembers its colorPop out in the code editor (opens in a new tab)JavaScript
function* trafficLight() {  let light = "red";  while (true) {    yield light;    light = light === "red" ? "green" : light === "green" ? "yellow" : "red";  }} const lights = trafficLight();console.log(lights.next().value);console.log(lights.next().value);console.log(lights.next().value);console.log(lights.next().value);

A state machine is something that is always in one state, and has rules for moving to the next one. A traffic light is a good example: red, then green, then yellow, then red again.

Line 2 starts with light set to red. Line 4 yields the current color and pauses. When next() is called again, line 5 picks the next color. Because light is kept between pauses, lines 10 to 13 print red, green, yellow, and red. This code only describes the order of the colors. It does not control a real traffic light.

When to use a generator, and when not to
  • Use an array when the data is small, has an end, and you need it more than once.
  • Use a generator when the code that asks may stop early, or when each next item takes real work to make.
  • Put the limit right next to an endless generator, like take(source, limit).
  • A generator object works only once. Call the generator function again to start over.
Pick out paid orders, one at a timePop out in the code editor (opens in a new tab)JavaScript
const orders = [  { id: 1, paid: true },  { id: 2, paid: false },  { id: 3, paid: true },]; function* paidOrderIds(items) {  for (const order of items) {    if (order.paid) yield order.id;  }} console.log([...paidOrderIds(orders)].join(", "));

Lines 1 to 5 list three orders. Lines 7 to 11 write paidOrderIds. Line 8 loops over the orders, and line 9 yields an order's id only if it is paid. Line 13 prints 1, 3.

For a short array that is already in memory, orders.filter(...).map(...) works just as well. The generator version helps when orders arrive one page at a time, or when the code that asks may stop after the first match.

Other places where generators show up:

  • Splitting text into words: yield one word at a time.
  • Walking a tree, like folders or a menu: yield each file, folder, or menu item as you find it.
  • Making test data: yield as many test cases as the test needs.
  • Chains of steps: combine filter, map, and take without making a new array between each step.

Common mix-ups

"Calling a generator runs it."

No. Calling it only makes a generator object. The code waits for the first next().

"A generator object is like an array I can reuse."

No. It hands out its values one at a time, and only once. When it is used up, it stays finished. Call the generator function again for a fresh object.

"return() skips the clean-up code."

No. It runs any waiting finally block first. That is why a generator can clean up when a loop stops early.

"yield* only works with generators."

No. It works with any iterable. The extra part with a generator is that yield* gives back that generator's final return value.

"Lazy code is always safer."

No. Lazy code only saves work if the code that asks for values stops. Spreading an endless generator is a bug: it never finishes.

The table below compares the three commands you can send to a generator. Read each row as: "when I call this, where does the generator carry on, and what do I get back?"

The three commands you can send to a generator
When you callWhere the generator carries onWhat you get back
next()At the start of its code, or right after the paused yield.The next yielded value with done: false, or done: true once the code has finished.
return(value)At the paused yield, but only to finish early.Usually { value, done: true }, after any finally block has run.
throw(error)At the paused yield, as if that line threw the error.If the generator catches it, its next result. If not, the error is thrown back to you.

Practice: generators

5 EXERCISES

Try each exercise before you open the hints. The first ones ask you to predict the output. The later ones ask you to write and fix generators.

Exercise 1 · Warm-upPredict two yields and the end

This program logs three values. Write them in order, separated by commas.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function* letters() {
  yield "A";
  yield "B";
}

const it = letters();
console.log(it.next().value);
console.log(it.next().done);
console.log(it.next().done);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWrite a generator of odd numbers

    Write a generator that yields the odd numbers 1, 3, 5, and so on, up to limit.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function* odds(limit) {
      // yield 1, 3, 5 up to limit
    }
    
    console.log([...odds(5)].join(","));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the never-ending bug

      This code never finishes. What is wrong with it, and how can you limit it safely?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function* ids() {
        let id = 1;
        while (true) yield id++;
      }
      
      console.log([...ids()].slice(0, 3));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeSave a yield* return value

        Predict what this program prints.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function* one() {
          yield "x";
          return "done";
        }
        function* two() {
          const message = yield* one();
          yield message;
        }
        console.log([...two()].join("|"));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeUse a generator for a real task

          Write a generator that takes a list of task objects like { title: "Fix nav", tags: ["ui"] }. It should yield only the titles of tasks whose tags include a given tag.

            Quiz: check your understanding

            7 QUESTIONS

            Pick an answer, then read the explanation for every choice. The wrong choices show common mistakes with generators.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7When does the code inside a generator function start running?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function* colors() {
                yield "red";
                yield "blue";
              }
              const list = colors();
              console.log(list.next().value);
              console.log(list.next().done);
              console.log(list.next().done);

              Choose an answer to see the explanation.

            3. Question 3 of 7Which statement about generator objects is true?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does return("stop") do to a paused generator?

              Choose an answer to see the explanation.

            5. Question 5 of 7What does yield* otherIterable do?

              Choose an answer to see the explanation.

            6. Question 6 of 7What does this yield* program print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function* inner() {
                yield "a";
                return "done";
              }
              function* outer() {
                const final = yield* inner();
                yield final;
              }
              console.log([...outer()].join(","));

              Choose an answer to see the explanation.

            7. Question 7 of 7Which line should you never run with an endless generator like ids()?

              Choose an answer to see the explanation.

            Key takeaways

            • function* makes a generator function. Calling it gives you a generator object and runs none of its code.
            • Each next() runs the code until the next yield, a return, or an error.
            • A generator object is both an iterator and an iterable, so it works with for...of and spread. It works only once.
            • return() finishes early and still runs finally. throw() throws an error at the paused yield.
            • yield* hands out every value from another iterable. With a generator, it also gives back that generator's return value.
            • An endless generator is safe only when something like take stops asking for values.

            Remember the one-liner.
            A generator is a function that can pause, and it makes each value only when you ask for it.

            Up next: Iterator helpers. They give any iterator lazy map, filter, take, and more.

            CompleteFrontend Clear concepts. Working examples.