Categories
Modern JavaScript

Best of Modern JavaScript — Iterators and Generators

Spread the love

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at JavaScript iterable objects and generator functions.

Clean Up of Generators

If we use break to break a loop, then we can clean up with the try-finally constructor.

For example, if we have:

function* genFn() {
  yield 'foo';
  yield 'bar';
  yield 'baz';
  console.log('clean up');

}

const gen = genFn();

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

Then the console log will run when the generator returned all the items.

However, if we use break in our loop:

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

Then the console log is never run.

We can run the cleanup step when we use break in our loop by wrapping the yield code with try-finally.

For example, we can write:

function* genFn() {
  try {
    yield 'foo';
    yield 'bar';
    yield 'baz';
  } finally {
    console.log('clean up');
  }
}

const gen = genFn();

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

We added the finally block, which will run when the break statement is run.

This is useful for running clean up code on our generator.

If we implement our own iterator, we can put the function in our iterator’s return method:

const obj = {
  [Symbol.iterator]() {
    function hasNextValue() {
      //...
    }

    function getNextValue() {
      //...
    }

    function cleanUp() {
      //...
    }

    return {
      next() {
        if (hasNextValue()) {
          const value = getNextValue();
          return {
            done: false,
            value: value
          };
        } else {
          //...
          return {
            done: true,
            value: undefined
          };
        }
      },
      return () {
        cleanUp();
      }
    };
  }
}

We have the return method included in the object we return with the Symbol.iterator method.

Closing Iterators

We can close any iterators by creating our own generator function to close it.

For instance, we can write:

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

We call break on the loop to end it with when the ending condition is met.

Generators

Generators are pieces of code that we can pause and resume.

It’s denoted by the function* keyword for generator functions.

yield is an operation that a generator used to pause itself.

Generators can receive input and send output with yield .

We can create a generator function by writing:

function* genFn() {
  yield 'foo';
  yield 'bar';
  yield 'baz';
}

This returns a generator object which we can call next on to return the values that are yield:

const genObj = genFn();
console.log(genObj.next());
console.log(genObj.next());
console.log(genObj.next());

We call genFn to return a generator.

Then we call next to get each value sequentially.

So we get:

{value: "foo", done: false}
{value: "bar", done: false}
{value: "baz", done: false}

We return an object with the value and done property.

The value has the value from yield .

done tells us whether all the values have been yield from the generator.

Conclusion

We can clean up our generator by adding a try-finally block.

Also, generator functions create generators that let us return the value sequentially.

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.