JavaScript 向迭代器添加 "return" 方法未正确关闭迭代器
JavaScript adding "return" method to an iterator doesn't properly close the iterator
我正在学习 JavaScript ES6 迭代器模式并遇到了这个问题:
const counter = [1, 2, 3, 4, 5];
const iter = counter[Symbol.iterator]();
iter.return = function() {
console.log("exiting early");
return { done: true };
};
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}
// 1
// 2
// 3
// exiting early
// ---
// 4
// 5
所以我向从数组中提取的迭代器添加了一个 return
方法定义。虽然调用了 return 方法,但它实际上并没有关闭迭代器。相比之下,如果我在定义中定义迭代器 return
方法,它将按预期工作:
class Counter {
[Symbol.iterator]() {
let count = 1;
return {
next() {
if (count <= 5) {
return {
done: false,
value: count++
}
} else {
return {
done: true,
value: undefined
}
}
},
return() {
console.log('exiting early');
return { done: true, value: undefined };
}
}
}
}
const myCounter = new Counter();
iter = myCounter[Symbol.iterator]();
for (let i of myCounter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of myCounter) {
console.log(i);
}
// 1
// 2
// 3
// exiting early
// ---
// 1
// 2
// 3
// 4
// 5
我的问题是,为什么会出现这种意外行为?我假设如果 return
方法没有被调用,那么迭代器将不会关闭,直到它通过调用 next
到达最后。但是添加 return
属性 将正确地“调用” return
方法,因为我得到了控制台日志,但实际上并没有终止迭代器,即使我 returned { done: true }
在 return
方法中。
你的例子可以简化为
let count = 1;
const iter = {
[Symbol.iterator]() { return this; },
next() {
if (count <= 5) {
return {
done: false,
value: count++
}
} else {
return {
done: true,
value: undefined
}
}
},
return() {
console.log('exiting early');
return { done: true, value: undefined };
}
};
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}
所以iter
只是一个普通对象。您将它传递给 for..of
循环两次。
您对迭代器接口的工作方式做出了错误的假设。核心问题是此代码中没有任何内容可以存储和跟踪 iter
已 returned done: true
一次的事实,因此应该继续这样做。如果那是你想要的行为,你需要自己做,例如
let count = 1;
let done = false;
const iter = {
[Symbol.iterator]() { return this; },
next() {
if (!done && count <= 5) {
return {
value: count++
}
} else {
done = true;
return { done };
}
},
return() {
done = true;
console.log('exiting early');
return { done };
}
};
for..of
循环本质上是调用 .next()
直到 return 结果为 done: true
,并在某些情况下调用 .return
。由迭代器本身的实现来确保它正确地进入“关闭”状态。
所有这些也可以通过使用生成器函数来简化,因为生成器对象具有内部“关闭”状态,自动包含为具有 returned 的函数的 side-effect,例如
function* counter() {
let counter = 1;
while (counter <= 5) yield counter++;
}
const iter = counter();
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}
你的两个 return
方法都没有真正关闭迭代器。为此,他们需要记录迭代器的新状态,并由此导致 next
方法在所有后续调用中也 return {done: true}
- 这就是“关闭”的实际含义.
我们可以在生成器中看到这种行为:
const iter = function*(){ yield 1; yield 2; yield 3; }();
console.log(iter.next());
console.log(iter.return());
console.log(iter.next());
您的第一个代码段存在问题,您已覆盖 iter.return
,您的方法被调用(从日志中可以看出)但它从未真正关闭 iter
。潜在的问题是数组迭代器 无法关闭 ,它们通常根本没有 return
方法。您还必须覆盖 iter.next
方法来模拟此方法。
你的第二个片段有问题,它实际上并没有尝试迭代 iter
,而是迭代了 myCounter
两次,这为每个循环创建了一个新的迭代器对象。相反,我们需要使用 [Symbol.iterator]
方法多次 return 同一个对象,最简单的方法是让 Counter
实现迭代器接口本身。我们现在可以重现意外行为:
class Counter {
count = 1;
[Symbol.iterator]() {
return this;
}
next() {
if (this.count <= 5) {
return {done: false, value: this.count++ };
} else {
return {done: true, value: undefined};
}
}
return() {
console.log('exiting early');
return { done: true, value: undefined };
}
}
const iter = new Counter();
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}
为了修复该行为,我们将通过 return
方法将计数设置为超过 5 来关闭迭代器:
class Counter {
count = 1;
[Symbol.iterator]() {
return this;
}
next() {
if (this.count <= 5) {
return {done: false, value: this.count++ };
} else {
return {done: true, value: undefined};
}
}
return() {
this.count = 6;
// ^^^^^^^^^^^^^^^
console.log('exiting early');
return { done: true, value: undefined };
}
}
const iter = new Counter();
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i); // not executed!
}
我正在学习 JavaScript ES6 迭代器模式并遇到了这个问题:
const counter = [1, 2, 3, 4, 5];
const iter = counter[Symbol.iterator]();
iter.return = function() {
console.log("exiting early");
return { done: true };
};
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}
// 1
// 2
// 3
// exiting early
// ---
// 4
// 5
所以我向从数组中提取的迭代器添加了一个 return
方法定义。虽然调用了 return 方法,但它实际上并没有关闭迭代器。相比之下,如果我在定义中定义迭代器 return
方法,它将按预期工作:
class Counter {
[Symbol.iterator]() {
let count = 1;
return {
next() {
if (count <= 5) {
return {
done: false,
value: count++
}
} else {
return {
done: true,
value: undefined
}
}
},
return() {
console.log('exiting early');
return { done: true, value: undefined };
}
}
}
}
const myCounter = new Counter();
iter = myCounter[Symbol.iterator]();
for (let i of myCounter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of myCounter) {
console.log(i);
}
// 1
// 2
// 3
// exiting early
// ---
// 1
// 2
// 3
// 4
// 5
我的问题是,为什么会出现这种意外行为?我假设如果 return
方法没有被调用,那么迭代器将不会关闭,直到它通过调用 next
到达最后。但是添加 return
属性 将正确地“调用” return
方法,因为我得到了控制台日志,但实际上并没有终止迭代器,即使我 returned { done: true }
在 return
方法中。
你的例子可以简化为
let count = 1;
const iter = {
[Symbol.iterator]() { return this; },
next() {
if (count <= 5) {
return {
done: false,
value: count++
}
} else {
return {
done: true,
value: undefined
}
}
},
return() {
console.log('exiting early');
return { done: true, value: undefined };
}
};
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}
所以iter
只是一个普通对象。您将它传递给 for..of
循环两次。
您对迭代器接口的工作方式做出了错误的假设。核心问题是此代码中没有任何内容可以存储和跟踪 iter
已 returned done: true
一次的事实,因此应该继续这样做。如果那是你想要的行为,你需要自己做,例如
let count = 1;
let done = false;
const iter = {
[Symbol.iterator]() { return this; },
next() {
if (!done && count <= 5) {
return {
value: count++
}
} else {
done = true;
return { done };
}
},
return() {
done = true;
console.log('exiting early');
return { done };
}
};
for..of
循环本质上是调用 .next()
直到 return 结果为 done: true
,并在某些情况下调用 .return
。由迭代器本身的实现来确保它正确地进入“关闭”状态。
所有这些也可以通过使用生成器函数来简化,因为生成器对象具有内部“关闭”状态,自动包含为具有 returned 的函数的 side-effect,例如
function* counter() {
let counter = 1;
while (counter <= 5) yield counter++;
}
const iter = counter();
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}
你的两个 return
方法都没有真正关闭迭代器。为此,他们需要记录迭代器的新状态,并由此导致 next
方法在所有后续调用中也 return {done: true}
- 这就是“关闭”的实际含义.
我们可以在生成器中看到这种行为:
const iter = function*(){ yield 1; yield 2; yield 3; }();
console.log(iter.next());
console.log(iter.return());
console.log(iter.next());
您的第一个代码段存在问题,您已覆盖 iter.return
,您的方法被调用(从日志中可以看出)但它从未真正关闭 iter
。潜在的问题是数组迭代器 无法关闭 ,它们通常根本没有 return
方法。您还必须覆盖 iter.next
方法来模拟此方法。
你的第二个片段有问题,它实际上并没有尝试迭代 iter
,而是迭代了 myCounter
两次,这为每个循环创建了一个新的迭代器对象。相反,我们需要使用 [Symbol.iterator]
方法多次 return 同一个对象,最简单的方法是让 Counter
实现迭代器接口本身。我们现在可以重现意外行为:
class Counter {
count = 1;
[Symbol.iterator]() {
return this;
}
next() {
if (this.count <= 5) {
return {done: false, value: this.count++ };
} else {
return {done: true, value: undefined};
}
}
return() {
console.log('exiting early');
return { done: true, value: undefined };
}
}
const iter = new Counter();
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}
为了修复该行为,我们将通过 return
方法将计数设置为超过 5 来关闭迭代器:
class Counter {
count = 1;
[Symbol.iterator]() {
return this;
}
next() {
if (this.count <= 5) {
return {done: false, value: this.count++ };
} else {
return {done: true, value: undefined};
}
}
return() {
this.count = 6;
// ^^^^^^^^^^^^^^^
console.log('exiting early');
return { done: true, value: undefined };
}
}
const iter = new Counter();
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i); // not executed!
}