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

Imagine your Generator function being like your tax office. The yield keyword is like the only person working there (“I know, hard to image, right?”). You call that person (it.next()) then he/she starts working until he/she found something to send you back (yield "Here you go"), now he/she stops working until you call again. Meanwhile you can do with "Here you go" whatever you see fit. Now you decide to call again, but this time you pass along some information (it.next("my tax report")). The tax office person uses your info for internal processing until he/she finally sends you something back again ( { done: false, value: "all good" } ) . And this can continue until you call the office so often, that he/she eventually won’t have any new info for you and consider her work done ( { done: true, value: undefined } ). Even if you call the tax office knowing that there is nothing new, they show the decency to continue answering your calls.

Cool, now you understand how yield in a Generator function works and how you can send and retrieve info. But just to be sure, let your brain go through these steps again to understand it even better:

  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 }

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 Contact me

I am Mathias, born 38 years ago in Heidelberg, Germany. Today I am living in Munich and Stockholm. I am a passionate IT freelancer with more than 14 years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I am founder of bosy.com, creator of the security service platform BosyProtect© and initiator of several other software projects.