I've seen a few iterators implemented in javascript, and every time it seems like they're off by one (there's obviously something I'm missing) Here's an example
const numbers = [1, 2, 3];
numbers[Symbol.iterator] = function() {
let idx = 0;
return {
next: function() {
return {
value: numbers[idx ],
done: idx > numbers.length,
};
},
};
};
// Run the custom iterator:
for (const num of numbers) console.log(num);
In this example, done
is set to true when idx > numbers.length
. However doesn't this mean that numbers[3]
will not be set as done = true
(even though there's no value for numbers[3]
? I don't fully understand why this code works.
Would appreciate any help — thanks!
CodePudding user response:
There are two things to realise:
done
does not indicate whether the returned value is the last one, but whether the iterator has no more value to return. As Mozilla contributors write:done
A boolean that's false if the iterator was able to produce the next value in the sequence.
idx
evaluates and then increments, and so the expression for thedone
property works with anidx
value that is one more than was used for setting thevalue
property.
So we have these objects returned:
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }
Alternative code without
side-effect
To eliminate the side effect of
mentioned in point 2, it maybe helps to look at this version, which produces the exact same {value,done}
objects:
const numbers = [1, 2, 3];
numbers[Symbol.iterator] = function() {
let idx = -1; // <--- one less, so we can first increment
return {
next: function() {
idx ; // First increment
return {
value: numbers[idx], // then use
done: idx >= numbers.length, // ...as expected
};
},
};
};
// Run the custom iterator:
for (const num of numbers) console.log(num);
CodePudding user response:
The first time you call the iterator it sets value
to numbers[0]
, increments idx
to 1
, then sets done
to 1 > numbers.length
. So it returns {value: 1, done: false}
The second time, it sets value
to numbers[1]
, increments idx
to 2
, and sets done
to 2 > numbers.length
. So it returns {value: 2, done: false}
.
The third time, it sets value
to numbers[2]
, increments idx
to 3
, and sets done
to 3 > numbers.length
. So it returns {value: 3, done: false}
.
The fourth time, it sets value
to numbers[3]
, increments idx
to 4
, and sets done
to 4 > numbers.length
. So this time it returns {value: undefined, done: true}
. This indicates that the iteration is complete.
The important thing is that the iterator doesn't set done: true
on the last element, it does this when you've called it the next time after having reached the end. This design is necessary because some iterators may not be able to tell when they're returning the last element -- they need to try to get the next element and detect failure.