Generators in JavaScript

A Generator is a function that can be paused and resumed at a later time, while having the ability to pass values to and from the function at each pause point. Generators are a special type of Iterator (see previous article).

Generators introduce new JavaScript syntax and keywords to use them. Generator functions are created using the function* syntax.

let count = 0;
function* myGen(start, end) {
  for(start; count < end; start++) {
    yield count++;
  }
}
const genIterator = myGen(0,10);
console.log(genIterator.next());
console.log(genIterator.next());
console.log(genIterator.next());

Did you notice, that invoking the Generator (myGen(0,10)) does not execute its containing code? Instead it returns an Iterator. And as we know from a previous article, an Iterator provides a next() method and can be looped over using a for..of loop.

When a value is consumed by calling the generator’s next method, the Generator function executes until it encounters the yield keyword. The rest of the Generator function code is not executed until you call next() again. The function can be called as many times as desired, and returns a new Iterator each time. Each Generator may only be iterated once.

Object {
  done: false,
  value: 0
}

Object {
  done: false,
  value: 1
}

Object {
  done: false,
  value: 2
}

Sending values to and from the Generator via yield

  1. You call it.next()
  2. Now the generator code executes until it reaches yield. Code execution stops.
  3. Yield always returns an object to the code “outside” where you called next() looking like this:
    { done: boolean, value: any }.

    So if for example the Generator code says yield "Hi", the returned object is { done: false, value: "Hi" }.
  4. Outside, you can store “Hi” like this:
    const genValue = it.next().value;
  5. Now let’s send a value to the Generator:
    it.next("Whatever")
  6. The value “Whatever” is now inserted into the yield where it left off processing last time:
    const fromOutside = yield "Hi";
    console.log(fromOutside); // "Whatever"
  7. Code executes until the next yield which returns the object as mentioned above. And so on, until processing is done.
function* myGenerator() {
  const inp = yield "Hi";
  console.log(inp);
  const name = yield;
  yield "My name is " + name;
}

const it = myGenerator();
console.log(it.next("A")); // { done: false,  value: "Hi" }
console.log(it.next("B")); // "B" { done: false,  value: undefined }
console.log(it.next("C")); // { done: false,  value: "My name is C" }
console.log(it.next("D")); // { done: true,   value: undefined }

Using return in your generator function does return an object containing the value but also setting the done property to true. So, returning using the return keyword instead of the yield keyword is an effective way to end the generator function.

for..of over Generator

Remember, that a Generator is just an Iterator, and as such you can simply loop over it using for..of:

function* myGen() {
  let x = 0;
  while(x < 4) {
    yield x;
    x++;
  }
}

for(let val of myGen()) {
  console.log(val);
}

// 0, 1, 2, 3

Yield delegation

Yield delegation is when a Generator calls another Generator:

function* firstGen() {
  yield 1;
  yield 2;
  
  return 4;
}

function* secondGen() {
  const firstGenReturnVal = yield* firstGen();
  yield 3;
  yield firstGenReturnVal;
}

const it = secondGen();
console.log(it.next()); // { done: false, value: 1 }
console.log(it.next()); // { done: false, value: 2 }
console.log(it.next()); // { done: false, value: 3 }
console.log(it.next()); // { done: false, value: 4 }

Another example using only one Generator

function normalFunction() {
  return ["one", "two", "three"];
}

function* gen() {
  yield* normalFunction();
}

const it = gen();
console.log(it.next()); // { done: false, value: "one" }
console.log(it.next()); // { done: false, value: "two" }
console.log(it.next()); // { done: false, value: "three" }

Early Generator completion

Is there a way to let a Generator complete earlier than it actually would without changing the Generator code itself? Yes, using it.return() or it.throw().

function* myGen() {
  let x = 0;
  while(x < 10) {
    yield x;
    x++;
  }
}

const it = myGen();
console.log(it.next()); { done: false, value: 0 }
console.log(it.return()); { done: true, value: undefined }
console.log(it.next()); { done: true, value: undefined }

Or you throw an Exception

function* myGen() {
  let x = 0;
  try {
    while(x < 10) {
      yield x;
      x++;
    }
  } catch (error) {
      console.error(error);
  }
}

const it = myGen();
console.log(it.next()); // { done: false, value: 0 }
console.log(it.throw("Oh oh")); // "Oh oh" { done: true, value: undefined }
console.log(it.next()); { done: true, value: undefined }

About Author

Mathias Bothe To my job profile

I am Mathias from Heidelberg, Germany. I am a passionate IT freelancer with 15+ years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I create Bosycom and initiated several software projects.