编辑:见较新接受的答案。我会保持它的作用/确实有效,当我能够破解解决方案时,我非常高兴。但是,正如您在接受的答案中看到的那样,最终的解决方案非常简单,现在它已被确定。
我注意到打字稿将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
破解
所以,我做了一些小技巧来允许捕获另一个迭代器——所以我不必重新实现迭代器。
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;
}
它有效!
你好
,富
删除break
,你会得到:
你好
世界
每次检查后yield
在输入这个答案时,我意识到更好的问题/解决方案是在原始生成器方法中找到。
我能看到将信息传递回原始可迭代对象的唯一方法是使用next(value)
. 因此,如果我们选择一些唯一值(比如Symbol.for("terminated")
)来表示终止,我们将上面的 return-hack 更改为 call 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
如果您链接一些额外的转换迭代器,则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]
。我对异步迭代器的情况特别感兴趣,尤其是在需要正确处理某些资源时。