Enter Your Email Address to Watch This Lesson

Your link to unlock this lesson will be sent to this email address.

Unlock this lesson and all 962 of the free egghead.io lessons, plus get JavaScript content delivered directly to your inbox!



Existing egghead members will not see this. Sign in.

ES6 (ES2015) - Generators

5:28 JavaScript lesson by

Generators in ECMAscript 6 are first-class coroutines that produce encapsulated suspended execution contexts. Whew! Yield values and iterate over them until no more values exist in the generator. We'll talk more about practical use later ;)

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

Generators in ECMAscript 6 are first-class coroutines that produce encapsulated suspended execution contexts. Whew! Yield values and iterate over them until no more values exist in the generator. We'll talk more about practical use later ;)

Avatar
Dzmitry Budzko

Hey, how did you do this bulk editing on 03:00 where you added value to 4 lines simultaneously?

In reply to egghead.io
Avatar
Brian

It's a feature of his text editor. You hit ctrl + left click.

In reply to Dzmitry Budzko
Avatar
Brian

Can't wait until the next generator lesson. I'd especially be interested in seeing a lesson on generators in conjunction with RxJS.

In reply to egghead.io
Avatar
Saiful

This may sound off topic, but up until this video, i've noticed that ecma script 6 looks a lot like python.. any reason or story behind it?

Avatar
James

I was following along with the ES6 lessons nicely up to this one. Then........wat?

Avatar
Jesse

John Lindquist mentions there will be follow up lessons on practical use cases of Generators. I realize there are whole courses on RxJS, etc but can we see some practical uses of Generators soon?

Avatar
James

John Lindquist mentions there will be follow up lessons on practical use cases of Generators. I realize there are whole courses on RxJS, etc but can we see some practical uses of Generators soon?

I'm wondering the same thing. There is an RxJS Observables + Generators lesson by Mr. CycleJS.

In reply to Jesse
Avatar
Michael

I was following along with the ES6 lessons nicely up to this one. Then........wat?

same

In reply to James
Avatar
James Bedont

lost the thread of the course on this video. I really could have used some sort of practical example of generators.

Avatar
Joel

lost the thread of the course on this video. I really could have used some sort of practical example of generators.

Hey James, generators had me scratching my head for a practical use for a long time. Then I saw them used for redux-sagas. Mind blown! http://joelhooks.com/blog/2016/03/20/build-an-image-gallery-using-redux-saga

In reply to James Bedont
Avatar
DANIEL

Please fix this sound. Twelve is very low volume, then when you come into thirteen it blows your eardrums out !!!

You make a generator by adding asterisks right here after the function keyword. I'll go ahead and add a log statement saying that You called 'next()'. You might think that if I called greet() that it would log out You called 'next()' but nothing happens.

function* greet(){
  console.log(`You called 'next()'`);
}

greet();

If we assign something like a greeter to this greet() and then we see what the greeter is you can see that the greeter is actually an object so it did not invoke this. It actually created an object which I can call next() on. When I callgreeter.next(), and I'll just call this next and log that out. You can see that next is {value: undefined, done: true}.

let greeter = greet();
console.log(greeter); // { next: [Function], throw: [Function] }
let next = greeter.next();
console.log(next);  // You called 'next()'
                    // { value: undefined, done: true }

You can also see that it finally invoked our log statement saying that You called next(). Now, this is undefined because we didn't yield anything from our generator. It's done because it's gone through all of the yield statements which are actually none right now.

If I yield a simple hello and run this again you can see now I get {value: hello, done: false}. It logged out our statement. It returned hello and it's not done yet. It will actually be done on the next pass through next.

function* greet() {
  console.log(`You called 'next()'`);
  yield "hello";
}

let greeter = greet();
console.log(greeter); // { next: [Function], throw: [Function] }
let next = greeter.next();
console.log(next);  // You called 'next()'
                    // { value: 'hello', done: false }

If I call next() again, and I'll just name this one done, and I call the log statement again, so I'll just log out done. You can see that I get hello on the first pass with done: false and undefined with done: true because there are no more yield statements after this one, meaning it had iterated and gone through all of the yield statements.

let greeter = greet();
console.log(greeter);       // { next: [Function], throw: [Function] } 
let next = greeter.next(); 
console.log(next);          // You called 'next()'
                            // { value: 'hello', done: false }
let done = greeter.next();
console.log(done);          // { value: undefined, done: true }

If you have multiple yield statements like How, are, and you and you run this with only calling next() once you can see that it stops after calling how. This, console.log('I'm not called until the second next'), is not called. That, console.log('Call me before "you?"'); not called, and that, console.log('Called when "done"'); not called.

function* greet(){
  console.log(`Generators are "lazy"`);
  yield "How";
  console.log(`I'm not called until the second next`);
  yield "are";
  console.log(`Call me before "you?"`);
  yeild "you?";
  console.log(`Called when "done"`);

  var greeter = greet();
  console.log(greeter.next()); // Generators are "lazy"
                               // { value: "How", done: false }
}

If you were to create any objects or anything inside of here they would not be created until you called next(), meaning that you can put stuff in here that's not created until you explicitly need it.

If we go ahead and call next() three more times and run it again you can see it logs out each log statement, then the yield, the log, then the yield. It logs this, then yields that, then logs this, then yields the next one.

Because it's an iterator you can also use the for/of syntax. We can say for(let word of greeter) and then log out the word and then run that. You can see the output is basically the same. How are you? The main difference is that this is grabbing the value off of the next.

for(let word of greeter) {
  console.log(word);  // Generators are "lazy"
                      // How  
                      // I'm not called until the second next
                      // are
                      // Call me before "you?"
                      // you?
                      // Called when "done"
}

To do that by calling next we'd have to revert this and say .value, then run it again and we get the same output.

console.log(greeter.next().value); // Generators are "lazy"
                                   // How
console.log(greeter.next().value); // I'm not called until the second next
                                   // are
console.log(greeter.next().value); // Call me before "you?"
                                   // you?
console.log(greeter.next().value); // Called when "done"

It's really easy to get mixed up when you start assigning things to yield statements. If I say let friendly = yield "how"; and then I try to log out friendly you might expect it to log out how because that's what it's yielding. But if I run this you'll see it's actually logging out undefined.

The way that this works is that the next() step through the iteration, so if I say " The heck", will basically send this back through and assign it to this friendly. If I log this out now you'll see I get How, and then The heck. That means you can start building things through the iteration process.

function* greet(){
  let friendly = yield "How";
  yield "are";
  yield "you?";
}

var greeter = greet();
console.log(greeter.next().value);              // How
console.log(greeter.next(" the heck ").value);  //  the heck
console.log(greeter.next().value);              // are
                                                // you?

If I yield friendly + are and then I assign friendly to this and then I yield friendly + "you" and I pass in " silly ole " and I run this again you can get How the heck are silly ole you? because this message is being returned here when the next step is run and assigned to this friendly. I yield that part of the friendly and then this comes from here, which is being assigned here. Then it yields that final statement.

function* greet(){
  let friendly = yield "How";
  friendly =  yield friendly + "are";
  yield friendly + "you?";
}

var greeter = greet();
console.log(greeter.next().value);              // How
console.log(greeter.next(" the heck ").value);  //  the heck are
console.log(greeter.next(" silly ol`").value);  //  silly ol'you?

One thing to note is that because this assignment happens on the run after the first one it's actually impossible to pass a value in here.

var greeter = greet();
console.log(greeter.next("first").value); // TypeError: Sent value to newborn generator

If I try and say "first" and run this I'll get an error saying, Sent value to a newborn generator, because you haven't given this a chance to run and iterate and go to the next() step where you could actually pass in a value.

Lastly, generators also help you work with infinite sequences. If I wrap my yield with a while(true), which is never going to stop looping, I can safely yield {x:x, y:y} point knowing confidently that this stuff isn't going to evaluate until the next step through after the yield process.

function* graph(){
  let x = 0;
  let y = 0;
  while(true){
    yield  {x:x, y:y}
    x += 2;
    y += 1; 
  }
}

var graphGenerator = graph();
console.log(graphGenerator.next().value);

It will safely pause instead of infinitely going through this while loop. When I run this, you can see I get zero, two, four, six, eight and so on. Zero, one, two, three, four, and so on.

// {x: 0, y: 0}
// {x: 2, y: 1}
// {x: 4, y: 2}
// {x: 6, y: 3}
// {x: 8, y: 4}
// {x: 10, y: 5}
// {x: 12, y: 6}
// {x: 14, y: 7}

I could generate these forever. They're also only created when I request them through the yield. They're not created ahead of time.

Don't worry. We will dive more into practical use cases of generators in future videos.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?