Generators are a new type of function in ES6. In asynchronous programming, generators solve the non-sequential, non-reasonable issues of callbacks. When a generator is called, it returns an iterator which will step through the generator, pausing anytime the yield keyword is encountered. After introducing the concept of a generator, Kyle shares a simple code example.
(upbeat pan flute) - [Kyle] A generator is a new exotic form of function introduced in ES6. And we're going to look at the actual mechanism first. Understanding some of how it works and that kind of thing. And at first as we're looking at it, it's not going to look like this has anything at all to do with asynchronous programming. It's going to seem a bit strange and out of place. I promise there's a reason why we're going through it. This is a fundamental part of the whole picture.
Which, the fancy way of saying it is that they have a run-to-completion semantic. What that means is that when a function starts running it will always run to the end of that function and finish before any other function has an opportunity to come in and start running. At any given moment only one function is actively executing. That doesn't mean it can't call to other functions but it means that nobody can come in and preemptively interrupt this function to run something else. It's a run-to-completion semantic.
So that might raise a few warning flags in your head when I tell you generators do not have a run-to-completion semantic. They're in a different kind of function altogether. It's a very different kind of beast. Now that's not designed to introduce chaos into your program. It's designed to introduce a behavior that we previously had to go to a lot of work to simulate. So it's adding a syntactic sugar, if you will, on top of a behavior that we would have had to spend quite a bit of time and effort to try to emulate.
So we can implement state machines without generators. And, I've done that many times. Some of you may have implemented your own state machines. But you have to go through a lot of work and you have to end up using closure around a function to remember the state as it, you know, it gets recalled over and over again. And each time it's recalled it remembers its previous state and it does the transition. So it's possible but it's harder and it's a lot less clear what's going on. A generator is a syntactic form of a state machine. And while that won't seem like that has anything to do with asynchronous programming it's actually a magical key that will unlock one of our really important characteristics that we're going for.
So to get there we want to start looking right at the very beginning at generators. And what we're going to see is that there's this new special keyword called 'yield'. The yield keyword is kind of like the pause button on an old-school VCR. You're watching a movie and you click the pause button. Literally pauses right in mid-frame, nothing changes, and you can wait as long as you want to. And then you can come along later and press the play button and resume. So you can think of a generator as a pausable function.
A function that, when it's running would run across a yield keyword and at that moment, wherever the yield keyword shows up, even right in the middle of an expression, everything just literally freezes. It pauses. And the generator enters this paused state. And it will wait for that pause state indefinitely. Until some other actor comes along and says it's time to resume. And they press the play button again. So you can think about a generator as a function that can pause and resume.
Pause and resume. As many times as necessary. While a generator is paused, on the inside of the generator, everything is completely blocked. Nothing is happening. But that's not blocking the overall program. It's a localized blocking. Only inside of the generator. That's a really key issue. So even though what I'm about to show you will look like we're creating a blocking sort of a thing. It's extremely localized. It's not blocking the entire program.
So, here's an example of a generator. You'll notice on line one we have that little star symbol there because we need to introduce a different kind of function with a different set of behavior to it. So there needs to be a syntactic clue to the engine. This is a different sort of a thing. So it's function star. That's how we indicate that it's a generator. You'll notice on line three that we have the yield keyword there. And that yield keyword is the pause button. So it's like the generator gets to decide when it wants to pause.
Nobody on the outside gets to tell the generator when to pause. So it cannot be preemptively interrupted. This is what we call cooperative concurrency rather than preemptive concurrency. So preemptive means somebody else on the outside, some outside force, can come in and interrupt you at any given moment. That's the thing that causes chaos and havoc in threaded languages. We don't have that. What we have is that the generator gets to decide, by virtue of calling a yield keyword, when and where and in what manner it wants to yield itself.
When it wants to pause itself. Now another thing that's different about a generator, from the aesthetic usage perspective, on line seven when I call g-e-n I'm calling what normally looks like just executing a function. And we typically would expect for it to execute the whole thing. But actually none of the generator runs at that point. Executing a generator does not actually run any of its code. Instead, it produces an iterator. Some of you hopefully have heard about iterators.
In this case calling a generator produces an iterator. And the purpose of the iterator in this case is not to step through data but rather to step through the control of our generator from the outside. So again, we can't pause the generator but we can ask the generator to run until it wants to pause. And then when we call dot next again we can resume it and then it'll pause again. And we can call dot next and it'll resume and then it'll pause again. So here on line seven, when I call g-e-n, none of the codes run.
But on line eight, when I call the first dot next call, I start it up on line two and I start running the generator. So I'm going to print out the 'hello' that you see there. And when it gets to the yield keyword on line three it's going to pause. And return control back to line eight. Well line eight could then proceed immediately to line nine and call dot next again and resume it. So you can synchronously step through it. But of course there could be a break. There could be a gap. There could be a delay in time between lines eight and nine. And in that period of time the overall program continues to run without change.
But what's different is that inside of this generator it's in this paused state. It's blocking. It's waiting for somebody to come along and call the dot next method again to resume it.
Note: This course was created by Frontend Masters. It was originally released on 3/29/2016. We're pleased to host this training in our library.
- Parallel and asynchronous code
- Working with callbacks
- Using thunks
- Exploring promise flow control
- Abstractions, sequences, and gates
- Observables, events, and sequences
- Blocking channels