A more generalized alternative, firmly based in composition principles, is implement another iterator using a generator function. This will, in fact, compose these two iterators.
The new iterator will basically delegate the actual yielding of the values to the original iterator. The difference is that the former will alyaws be “one step ahead” of the latter. By doing so, the new iterator will be capable of verify, for each element, if the iteration has stopped with the next one.
function* withLast(iterable) {
const iterator = iterable[Symbol.iterator]();
let curr = iterator.next();
let next = iterator.next();
while (!curr.done) {
yield [next.done, curr.value];
[curr, next] = [next, iterator.next()];
}
}
Notice that, in the first iteration, next
is called twice. For each of the following iterations, next
is always called once, only for the element that corresponds one step ahead of the current iteration.
Learn more about the JavaScript iteration protocols to learn more about that implementation.
A little more concrete example:
function* withLast(iterable) {
const iterator = iterable[Symbol.iterator]();
let curr = iterator.next();
let next = iterator.next();
while (!curr.done) {
yield [next.done, curr.value];
[curr, next] = [next, iterator.next()];
}
}
const arr = [1, 2, 3];
for (const [isLast, element] of withLast(arr)) {
console.log(`${element} ${isLast ? 'is' : 'is not'} the last.`);
}
As concepts like index or the actual length of the iterator – the latter which isn't even a thing in some corner cases as infinite iterators – aren't used in this solution, this approach is well suited for any kind of iterator.
Another example:
function* withLast(iterable) {
const iterator = iterable[Symbol.iterator]();
let curr = iterator.next();
let next = iterator.next();
while (!curr.done) {
yield [next.done, curr.value];
[curr, next] = [next, iterator.next()];
}
}
// Generator function that creates a "generic" iterator:
function* gen() {
yield 'a';
yield 'b';
yield 'c';
}
for (const [isLast, element] of withLast(gen())) {
console.log(`${element} ${isLast ? 'is' : 'is not'} the last.`);
}