JavaScript Generator Basics
By: Chris Hosler
Tags: JavaScript, Javascript Development, Software Engineering, Software Development

On my never-ending search of neat things in JavaScript, I bring you…Generators. A generator looks like a regular old function but acts like an iterator. Another neat thing about generators is that they can pause what they are doing and resume, where it was, it at a later time.

Let’s start out by just looking at a regular function…

function cannotBeStopped(){

console.log("Can't");

console.log("Stop");

console.log("Now");

}

If we run this function, it will execute from the top down giving us the output…

"Can't"

"Stop"

"Now"

Running it again will give us the exact same behavior, the function will execute from top to bottom, giving us the same output.

Now, generators, or generator functions, give us a little bit more flexibility. This type of function, will allow us to pause the function and pick up where we left off. Let’s take a look at one…

function* immaGenerator(){

console.log("I'm the 1st output");

yield 1;

console.log("I'm the 2nd output");

yield 2;

}

To break this down before we run it, the * after function denotes that our immaGenerator() is, indeed, a generator, and the yield is what pauses our generator and returns a value.

To invoke our generator we’re going to do something like…

let generator = immaGenerator();

This invocation will output nothing to the console (a regular function would’ve just ran off and gave us our console.log messages), but it does give us a returned value. Let’s check what that value is by running…

console.log(generator)

…gives us…

Object [Generator] {}

We get a Generator object back, now the generator was invoked but did not run the methods inside of it’s body. This Generator object will now return another object with two properties: value and done, showing us that the Generator object is iterable.

To get into the body of our generator we are going to use the next() method, this will, essentially, step through the body of our generator and pause when it hits a yield . So running…

let result = generator.next();

console.log(firstResult);

…will output…

I'm the first output

{value: 1, done: false}

We hit our first console.log and we get our returned object which includes: our yield value (that we assigned as 1, we could have given it any value, e.g. “Hello” , true , 1000 , etc.) and done: false (since the generator has not run all of the methods in it’s body).

To step through it again, we use the next() method, well, again…

result = generator.next();

console.log(result);

…this gives us…

I'm the 2nd output

{value: 2, done: false}

This hits our second console.log , hits our second yield which outputs our new object {value: 2, done: false} , and pauses again. Running next() again…

result = generator.next();

console.log(result);

…yields…

{value: undefined, done: true}

Which is the end of our generator function. Just for reference here is the full implementation…

function* immaGenerator(){

console.log("I'm the 1st output");

yield 1;

console.log("I'm the 2nd output");

yield 2;

} let generator = immaGenerator(); let result = generator.next; result = generator.next; result = generator.next();

console.log(result);

A few things to note, once the generator is complete it can no longer generate values, as such generators are kind of a one-shot deal. If you need to generate more values, you’ll need a whole new generator. Another thing is that while we are using yield to produce values, we’re good, however if we throw a return statement into our generator, it will also stop producing values because return sets our done property to true . For example…

function* immaGenerator(){

console.log("I'm the 1st output");

yield 1;

console.log("I'm the 2nd output");

return 2;

console.log("I won't run");

yield 3;

} let generator = immaGenerator() let result = generator.next(); result = generator.next()

result = generator.next() console.log(result)

…outputs…

I'm the 1st output

I'm the 2nd output

{ value: undefined, done: true }

We’ll hit our first two console.log statements and get our first two values but that’s it, we’ll never hit our yield 3 .

For…of Loop

Since the generator function is also iterable we could also use a for…of loop with it, as such…

function* immaGenerator(){

console.log("I'm the 1st output");

yield 1;

console.log("I'm the 2nd output");

yield 2;

} for (const g of immaGenerator()) {

console.log(g);

}

…which outputs…

I'm the 1st output

1

I'm the 2nd output

2

Infinitely Repeating Generator

For another example of a generator function, this generator will create a sequence that will never-end…

function* keepsCounting() {

let index = 0;

while (true) {

yield index++;

}

} let k = keepsCounting(); console.log(k.next()); // {value: 0, done: false}

console.log(k.next()); // {value: 1, done: false}

console.log(k.next()); // {value: 2, done: false}

Every time we call the next() method our generator will continue to count starting from 0 and incrementing by 1 .

One practical application that I found for using an infinite generator is to create unique ids, calling the next() method every time you need to grab a new id. Just to tweak our example…

function* createId() {

let index = 0;

while (true) {

yield index++;

}

} let id= createId(); console.log(id.next().value); // 0

console.log(id.next().value); // 1

console.log(id.next().value); // 2

The upshot of using generators in this way is that it uses Lazy Evaluation, in this type of evaluation model, values are only created when we request them to be created, i.e. by calling the next() method. Since the value is not being calculated until we request it, this means that this value does not yet exist, making generators memory efficient.

Conclusion

That pretty sums up everything that I wanted to talk about, in regards, to generators. Some final conclusions:

Generators are functions that can be paused and resumed.

Generators are created by using the function* f(){} format.

Generators are iterable and can be used with the for…of loop.

Generators are memory efficient, since they do not calculate all values at once.

I hope that you found generators as fascinating as I did, and I hope that you find this information useful. Thank you for reading.