编辑:请查看新的已接受答案。我将保留这个答案,因为它是有效的/曾经有效,而且当我能够破解一个解决方案时,我感到非常高兴。然而,正如您在已接受的答案中所看到的那样,最终的解决方案现在变得如此简单,因为它已被确认。
我注意到 typescript 将Iterator
(lib.es2015) 定义为:
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}
我拦截了这些方法并记录了调用,如果迭代器被提前终止--至少通过一个
for-loop
--那么
return
方法就会被调用。
如果消费者抛出一个错误也会被调用。如果循环允许完全迭代迭代器,则不会调用
return
。
Return
hack
所以,我进行了一些hack,以允许捕获
另一个可迭代对象 - 这样我就不必重新实现迭代器。
function terminated(iterable, cb) {
return {
[Symbol.iterator]() {
const it = iterable[Symbol.iterator]();
it.return = function (value) {
cb(value);
return { done: true, value: undefined };
}
return it;
}
}
}
function* source() {
yield "hello"; yield "world";
}
function source2(){
return terminated(source(), () => { console.log("foo") });
}
for (let item of source2()) {
console.log(item);
break;
}
而且它有效!
你好
foo
删除 break
你将得到:
你好
世界
每次 yield
后检查
在回答这个问题的时候,我意识到一个更好的问题/解决方案是在原始生成器方法中找出。
我唯一能想到向原始可迭代对象传递信息的方法是使用 next(value)
。因此,如果我们选择一些独特的值(比如 Symbol.for("terminated")
)来表示终止,并且修改上面的返回窍门以调用 it.next(Symbol.for("terminated"))
:
function* source() {
let terminated = yield "hello";
if (terminated == Symbol.for("terminated")) {
console.log("FooBar!");
return;
}
yield "world";
}
function terminator(iterable) {
return {
[Symbol.iterator]() {
const it = iterable[Symbol.iterator]();
const $return = it.return;
it.return = function (value) {
it.next(Symbol.for("terminated"));
return $return.call(it)
}
return it;
}
}
}
for (let item of terminator(source())) {
console.log(item);
break;
}
成功!
你好
FooBar!
级联串联迭代器中的返回值
如果您链接一些额外的转换迭代器,那么 return
调用将级联通过它们:
function* chain(source) {
for (let item of source) { yield item; }
}
for (let item of chain(chain(terminator(source())))) {
console.log(item);
break
}
你好
FooBar!
包
我将上面的解决方案封装成了一个包。它支持[Symbol.iterator]
和[Symbol.asyncIterator]
两种迭代器。特别是在一些资源需要正确释放的情况下,异步迭代器非常有意义。
console.log('foo')
移动到yield 'hello'
上面,会有什么不同吗? - mikkelrd