Categories
Modern JavaScript

Best of Modern JavaScript — Closable Iterators

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.

Closable Iterators

An iterator is closable if it has the return method.

This is an optional feature.

Array iterators aren’t closable.

For instance, if we write:

const arr = ['foo', 'bar', 'baz'];
const iterator = arr[Symbol.iterator]();
console.log('return' in iterator)

We see that there’s no return method.

On the other hand, generator objects are closable by default.

For instance, if we have:

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

const gen = genFn();

We can finish calling the generator gen by calling the return method on it.

For instance, we can write:

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

const gen = genFn();
console.log(gen.next());
gen.return();
console.log(gen.next());
console.log(gen.next());

After we called return , then the generator is done.

So the last 2 return calls give us:

{value: undefined, done: true}

If an iterator isn’t closable, we can continue iterating over it after an exit from a for-of loop.

So if we have an iterable with a non-closable iterator like an array, we can continue looping through it:

const arr = ['foo', 'bar', 'baz'];

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

After we used break to break out of the first loop, the 2nd loop still iterate from start to end.

Prevent Iterators from Being Closed

We can prevent iterators from being closed by changing the return method.

If we return done set to false , then it can’t be closed.

For example, we can write:

class NoReturn {
  constructor(iterator) {
    this.iterator = iterator;
  }
  [Symbol.iterator]() {
    return this;
  }
  next() {
    return this.iterator.next();
  }
  return (value) {
    return {
      done: false,
      value
    };
  }
}

function* genFn() {
  yield 'foo';
  yield 'bar';
  yield 'baz';
}
const gen = genFn();
const noReturn = new NoReturn(gen);

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

Since we passed our generator, which is closeable, to the NoReturn constructor, it’ll continue iterating through the items.

So we get:

foo
bar
baz

logged.

It just continues the iteration from where it left off since it hasn’t been closed.

So it can continue iteration from where it left off.

We can also make generators unclosable by setting the prototype.return method to undefined .

However, we should be aware that this doesn’t work with all transpilers.

For instance, we can write:

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

genFn.prototype.return = undefined;

We set the genFn.prototype.return property to be undefined .

Then we can confirm that it’s unclosable by writing:

const gen = genFn();

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

We get:

foo
bar
baz

from the 2 loops.

Using break didn’t close the generator.

Conclusion

We can adjust whether we can close an iterator.

If closing is disabled, this will let us loop through the iterator items after breaking and similar operations.

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.