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.