Categories
Modern JavaScript

Best of Modern JavaScript — Generator Functions

Spread the love

ince 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at JavaScript generators.

Implementing Iterables via Generators

Iterables can be implemented with generators.

We can create an iterable object with the Symbol.iterable method to make it iterable.

For instance, we can write:

const obj = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
}

We have the yield keyword in our Symbol.iterator method.

Then we can write:

for (const x of obj) {
  console.log(x);
}

to iterate through the values, and we get:

1
2
3

obj[Symbol.iterator] is a generator method that yields the values we can iterate through.

for-of use the method as an iterator to get the values.

Infinite Iterables

We can make itrerables that are infinite with generators.

For instance, we can write:

const obj = {
  *[Symbol.iterator]() {
    for (let n = 0;; n++) {
      yield n;
    }
  }
}

function* take(n, iterable) {
  for (const x of iterable) {
    if (n <= 0) {
      return;
    }
    n--;
    yield x;
  }
}

for (const x of take(5, obj)) {
  console.log(x);
}

Our iterable obj object has the Symbol.iterator method that yields integers.

Then we created the take function to yield the first n items from the iterable .

And finally, we loop through the returned variables.

Generators for Lazy Evaluation

Generators are useful for lazy evaluation.

For instance, we can create a function that yields the individual characters from a string.

We loop through the string and yield the keys.

For example, we can write:

function* tokenize(str) {
  for (const s of str) {
    yield s;
  }
}

let c;
const gen = tokenize('foobar');

while (c = gen.next().value) {
  console.log(c);
}

and we can get the values from the object.

Inheritance and Iterators

We can implement inheritance with generator functions like any other function.

For example, we can add an instance method to our generator by writing:

function* g() {}
g.prototype.foo = function() {
  return 'bar'
};
const obj = g();
console.log(obj.foo());

We added the foo method to the prototype property.

Then we call g generator function return the generator.

Then we can call the foo method on it.

The console log should have 'bar' logged.

If we try to get the prototype of an iterator.

For instance, we can write:

const getProto = Object.getPrototypeOf.bind(Object);
console.log(getProto([][Symbol.iterator]()));

Then we see the Array Iterator object.

It has the next method and other properties of the iterator’s prototype.

this in Generators

Generator functions have their own value of this .

It’s a function that sets up and returns a generator object.

And it contains the code that the generator object steps through.

We can look at the value of this by writing:

function* gen() {
  yield this;
}

const [genThis] = gen();
console.log(genThis)

If it’s at the top level and we get the value of this as we did with destructuring, we get the window object.

This is assuming that strict mode is off.

If strict mode is on:

function* gen() {
  'use strict';
  yield this;
}

then genThis is undefined .

If our generator is in an object:

function* gen() {
  'use strict';
  yield this;
}

const obj = {
  gen
}

const [genThis] = obj.gen();
console.log(genThis);

The genThis is the object itself.

Generator functions are like traditional functions.

Conclusion

Iterables can be implemented with generators.

Also, we can implement inheritance and get the value of this with generator functions.

Leave a Reply

Your email address will not be published.

If you like the content of this blog, subscribe to my email list to get exclusive articles not available to anyone else.