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:
donedoes not indicate whether the returned value is the last one, but whether the iterator has no more value to return. As Mozilla contributors write:doneA boolean that's false if the iterator was able to produce the next value in the sequence.
idxevaluates and then increments, and so the expression for thedoneproperty works with anidxvalue that is one more than was used for setting thevalueproperty.
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.
