A generator is a function that can be entered and exited multiple times. Its context gets saved across re-entrances allowing to control the iterator and hence create iterations through custom objects. This is unlikely to make much sense at this stage so let's look at an example.

In the example below we have a regular for-loop wrapped within a generator. It's very similar to a regular for-loop function except for the fact that we yield a result within each for-loop cycle. yield causes the generator to pause and return a given value. Then if we want to get the next value, we must resume the generator by invoking next() function. This demonstrates the biggest advantage of generators where we can get the next value from within the generator only when we really need it.

function *forLoopGenerator(num) {
  for (let i = 0; i < num; i += 1) {
    yield console.log(i);
  }
}

const gen = forLoopGenerator(3);

gen.next(); // 0
gen.next(); // 1
gen.next(); // 2

Notice that yield works a bit differently from return. When using return within a function, we simply return that value from a function and end the execution of that function.

With yield we also return a value and escape the function but we keep that function's context in memory. The next time we call the same function it will move on to the next yield statement and return a new value. It will keep doing so until there is nothing to yield/return anymore.

yield for iteration

Now let's make something useful out of generators. One cool thing we can built is an iterator that can be used from within the for..of loops. Imagine we have an object where each key represents a position, and its value is the name of the member. To iterate over such a structure we can build a custo generator and parse it to a for..of loop. See full example below.

const team = {
  founder: 'John',
  lead: 'Tom',
  frontend: 'Marc',
  backend: 'Lucas',
  admin: 'Tim'
}

function *teamGenerator(team) {
  yield team.founder;
  yield team.lead;
  yield team.frontend;
  yield team.backend;
  yield team.admin;
}

for (const member of teamGenerator(team)) {
  console.log(member);
}

Now, this is cool and all. But what if our team structure is more complex and contains an object within an object? Then we can use delegation of generators by making use of yield* generator() syntax where generator() is a call to a generator function which we want to delegate to. This allows to keep one for..of loop for two separate objects. Pretty neat!

const support = {
  'supportLevel1': 'David',
  'supportLevel2': 'Jasmine'
}

const team = {
  support,
  founder: 'John',
  lead: 'Tom',
  frontend: 'Marc',
  backend: 'Lucas',
  admin: 'Tim'
}

function *teamGenerator(team) {
  yield team.founder;
  yield team.lead;
  yield team.frontend;
  yield team.backend;
  yield team.admin;
  yield* supportGenerator(support);
}

function *supportGenerator(support) {
  yield support.supportLevel1;
  yield support.supportLevel2;
}

for (const member of teamGenerator(team)) {
  console.log(member);
}

We can also simplify the above syntax by using [Symbol.iterator] key on an object to declare a generator from within the object. See below example to see how the syntax works. Essentially, almost nothing changes except that now we can call for..of loop directly on the team object rather than a generator to which we passed the given object.

const support = {
  'supportLevel1': 'David',
  'supportLevel2': 'Jasmine',
  [Symbol.iterator]: function *() {
    yield this.supportLevel1;
    yield this.supportLevel2;
  }
}

const team = {
  support,
  founder: 'John',
  lead: 'Tom',
  frontend: 'Marc',
  backend: 'Lucas',
  admin: 'Tim',
  [Symbol.iterator]: function *() {
    yield this.founder;
    yield this.lead;
    yield this.frontend;
    yield this.backend;
    yield this.admin;
    yield* support;
  }
}

for (const member of team) {
  console.log(member);
}

Passing arguments into generators

Note that it's also possible to pass arguments into generators. However, it doesn't work exactly as you may expect . The yield x expression first sends x value out when pausing the generator function. And only when the generator is restarted, whatever value is sent in will be the result of that expression. If no value is sent, then the value will stay as undefined. Super confusing behavior if you ask me but that's how it works.

function *foo(x) {
  let y = yield x;
  yield y;
}

const gen = foo(5);

console.log(gen.next()); // { value: 5, done: false }
console.log(gen.next(12)); // { value: 12, done: false }
console.log(gen.next()); // { value: undefined, done: true }

Generators with recursion

One really cool application area of generators is recursion. Imagine we have a tree data structure with comments containing other comments. A really simple way to create such data structure is by using a regular class containing the content of a comment, its children contents, and an iterator that would first yield comment's content and then delegate a generator function to iterate over all its children. If a child has more children, iterate over those children and so on. There is nothing really new in the below example, only it has been applied in a new and interesting way. See the example below for full code.

class Comment {
  constructor(content, children) {
    this.content = content;
    this.children = children;
  }

  *[Symbol.iterator]() {
    yield this.content;
    for (const child of this.children) {
      yield* child;
    }
  }
}

const grandchildren = [
  new Comment('grandchildren comment 1', []),
  new Comment('grandchildren comment 2', []),
]

const children = [
  new Comment('children comment 1', grandchildren),
  new Comment('children comment 2', []),
  new Comment('children comment 3', []),
]

const tree = new Comment('parent comment 1', children);

for (const comment of tree) {
  console.log(comment);
}

Summary

ES6 generators are a really powerful new feature of ES6 and I only scratched the surface here. There is a lot more you can do with generators but I will perhaps leave it for another post. Personally, I haven't yet seen generators used in any production code and haven't used them myself either. However, it is interesting to see how powerful JavaScript can be and I hope I will come across a situation where I can make use of generators.