Skip to content

JavaScript generators

JS Generators Cover

Generators are yet another addition to the JS standard introduced in the ES6 edition of the language. Generators are a new addition to JavaScript, although they have been existing in other programming languages such as Python, PHP, and C#.

What is a generator?

Generators are functions. Generators are special types of functions. Regular functions execute from start to finish, producing one value (or nothing). On the other hand, generators can produce/yield multiple values, per request, and they can suspend their execution between these requests. Generators are useful for creating more elegant asynchronous code and data streams.

But why do we need asynchronous code? You probably already know this, but JavaScript is single-threaded, and single-threaded execution model can block the entire UI is if the code is sluggish or unresponsive, or if the API takes a long time to complete. That makes the whole application slow and hell to interact with.

So how does a generator work?

A generator generates (duh!) a sequence of values, on a per request basis. The code will ask the generator specifically for a new value, and the generator function will either produce the new value or notify us that there are no more values to give back. The execution is suspended until a new value is requested and the generator continues where it stopped.

Let us see how this works by looking at a simple coding snippet:

function* CarGenerator() {
  yield "BMW";
  yield "Mercedes";
  yield "Toyota";
  yield "Ford";
}

for (let car of CarGenerator()) {
  console.log(car);
}

Generator syntax

The first thing that ‘catches’ our eye is the asterisk next to the function keyword. It creates a generator function and enables us to use the yield keyword inside the body of the function. By decorating the function up with an asterisk, and yielding values inside the body we actually did everything we need in order to consume the generator. In the case above we iterated through the generator (like we would in an array!) and we got our yielded values back.

Let us rewrite the snippet above in order to make it a bit more interesting.

function* CarGenerator() {
  yield "BMW";
  yield "Mercedes";
  yield "Toyota";
  yield "Ford";
}

let carIterator = CarGenerator();

console.log(carIterator.next());

/* 
{
  done: false,
  value: "BMW"
}
*/

console.log(carIterator.next());

/*
{
  done: false,
  value: "Mercedes"
}
*/

Generators are iterable! Something interesting happened when we created an iterator with let carIterator = CarGenerator();. With the iterator, we get the ability to control the execution of the generator. We exposed the next() method which is used to request new values from the generator. But what value?

Well, as you can see in the snippet above, the values that are returners are values connected to the yield keyword, and those are value and done. When we encounter a yield keyword the generator returns a new object with the value of the yield and the information of whether we reached the end of the iterator. The generator will stop its execution and wait for the next next() method, which will wake the generator up and continue where it left off – producing new values!

So what happens if we have no more elements to produce. Let us take a look.

function* CarGenerator() {
  yield "BMW";
  yield "Mercedes";
  yield "Toyota";
  yield "Ford";
}

let carIterator = CarGenerator();

console.log(carIterator.next());
console.log(carIterator.next());
console.log(carIterator.next());
console.log(carIterator.next());

console.log(carIterator.next());
/*
{
  done: true,
  value: undefined
}
*/

The moment there is no more code to produce, the generator will return the ‘ending’ object, setting undefined for the value, and true for done.

Iterating a generator

We can walk through a generator an array using any kind of loop. Remember, the generator is iterable. Every time we can request a new value using next(), until the last object produces a false value for done.

function* CarGenerator() {
  yield "BMW";
  yield "Mercedes";
  yield "Toyota";
  yield "Ford";
}

let carIterator = CarGenerator();

let next;
while (!(next = carIterator.next()).done) {
   console.log(next);
}

We again create an iterator object by calling the generator function, and then we create the next variable in which individual products of the generator will be stored. The loop might seem a bit contrived but it is really simple. On each iteration we call the next() method of our iterator, and that value is stored in the conveniently named next variable. Additionally, we check whether we reached the last iterator object. If the done property is false, we keep on looping the generator.

Sending data to the generator

Above we have seen how we can produce values from a generator using the yield keyword. But we can also achieve two-way communication by sending data to the generator. This is useful in cases in which we produce a certain value, use that value to generate a new value, and then send that newly generated value back to the generator. We do that by simply using arguments like we would in any other regular function.

function* CarGenerator(property, value) {
  let carObject = {};
  carObject[property] = value;

  let newCar = yield carObject;

  yield newCar;
}

let carIterator = CarGenerator("color", "blue");
let carObject = carIterator.next();
console.log(carObject);

let updatedCarObject = {
  color: "pink"
}
let carIteratorObject2 = carIterator.next(updatedCarObject);
console.log(carIteratorObject2)

A cool thing to notice is that we sent values to the generator two times, initially by setting the color to blue and yielding the first value of carObject. Then we set a new object with the color pink and yielded newCar inside the generator method, which produces a completely different object! The generator function accepts initial arguments and any additional passing of arguments every time we call the next() method. Then we wake up the generator and resume the execution using the passed in value.

Final Words

Although Promises might be more widely used async structures the generators are also pretty important. There are other more advanced generator concepts to learn. For example how to yield to another generator, by delegating the execution of one generator to the other. If you are interested in learning more please click on the link.

Otherwise for more articles please click below, or check the blog.