The Basics of JavaScript Generators

May 17, 2019 0 Comments

The Basics of JavaScript Generators



ES6 generator functions are the functions which can stop its execution in the middle and can resume execution further from the same point. These functions do not return a single value, instead, it is capable of returning multiple values. They are based on the concept of iterators.

Below is the simple function in JavaScript without generators. A normal JavaScript function starts executing as soon as it is invoked. These function “Runs for Completion”, before any other code can execute.

We usually expect that the function will finish its execution before any other JavaScript function can run. A normal function starts execution and halts only when the execution is complete.

A function can stop execution in one of the following scenarios:

  1. The compiler reaches the end of the function.
  2. The return keyword is encountered.
  3. The function throws some error/exception.

In the case of Generators, these functions are capable of returning multiple values, and they may not execute completely. Function execution can be triggered in parts. It can stop execution in the middle and resume some other task and, after a while, it can resume the function from where it left off. Let's see a basic example of generators before we proceed further.

Generator functions can pause and can be resumed one or multiple times, which makes it interesting and very different from normal functions. While the function is paused, it allows other functions and code to execute. By default, generators are asynchronous in nature.

The code above contains a simple generator implementation. When a generator is called, it returns an “iterator”. The * after the function keyword is used to indicate that it is a generator and an iterator is returned from the function. This iterator won't fetch results by itself. It needs to be triggered from outside.

Once the Iterator has been created, we need to trigger this iterator to fetch data from the generator. We can continue execution on the function by calling the next method on the returned iterator to retrieve the value from the generator function. Each time the next() function is called on the iterator, it starts executing the function until it reaches the next yield keyword. After each yield, it stops processing further.

Once the execution halts, we need to re-trigger the next function on the iterator. When the next function is triggered on the iterator, it resumes the execution until the next yield is encountered and gives “2” as output.

ES6 generator functions cannot be interrupted from outside. Until and unless the yield is encountered during execution, the generators cannot be halted. If the generator function does not contain any yield keyword, it will behave like a normal function which executes to completion.

In a program below, it continues executing the generator function when next() is encountered on the returned iterator. It starts executing the function and halts on line 3 when the first yield is encountered. The yield keyword can return a value to the calling next() function. In this case, the first yield keyword would return a value “1” along with the flag signifying whether the generator execution is complete or not.

Since the generator is still not complete, it will return the flag done value as false. Yield returns key/value pair containing value and done as the keys.

So the output returned is: { value: 1, done: false }

“1” represents the value which is paired with the yield keyword and since the generator can yield further values, the done property is still false.

The next function can be called multiple times on the iterator, the flag done will indicate whether the generator is complete or not. Once the generator has finished execution, all further output will show the value of done flag to be true, and the value field to be undefined.

Calling the next function will resume the execution of generator until the next yield is available. On line 24, all the yield keywords are exhausted, so in this case, the returned value would be undefined (since function do not return anything) with the done flag set to true.

A generator function can have a return statement, as soon as the generator function encounters the return keyword, it updates the done flag to true, and even if we have more yield keywords in the function, the generator will be considered complete.

Once the return keyword is executed, all other yield would return undefined with done flag set as true.

We have seen that when we are passing value to yield keyword, it returns the value to the calling function next. However, generators enable 2-way communication. The next function can pass data to the generators too.

In code below, you can see that in line 15, we are passing a parameter to the next function. Now let's understand, how this line of code would execute.

Below are the series of events that occur when first call to next() is made:

  1. When next is called first time on the iterator, the generator starts execution
  2. The generator code executes until first yield keyword is encountered.
  3. It will return what is passed to yield function.

Output: { value: 1, done: false }

When next(10) is invoked:

  1. The parameter passed is substituted in place of yield 1

So effectively, line 3 in the generator becomes: let x = 10

2. It executes function further until the next yield is encountered.

3. It substitutes the value of x to 10 and returns the resulting value (10 +1)

Output: { value: 11, done: false }

On the following call of next():

  1. Since there is no further yield keyword, it returns undefined as the return value
  2. And since the generator is complete, done flag is set to true

Output: { value: undefined, done: false }

Since the generator functions return iterators, these iterators can be used inside a for..of loop. The Iterator will iterate through all the values present in the generator function. Parameters passed to the yield keyword will be passed as a value to the iterator.

I hope now you are more comfortable with using generator functions. Generators are amazing and can resolve various use case scenarios. Try to find out scenarios in your current application where generators will ease things out for you. I hope this article helped you!

Tag cloud