我注意到 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黑客
所以,我做了一些小技巧来允许捕获 another 可迭代的 - 所以我不必重新实现迭代器。
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 更改为调用 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!
包
我已经包装了上述解决方案as a package。它同时支持[Symbol.iterator] 和[Symbol.asyncIterator]。我对异步迭代器的情况特别感兴趣,尤其是在需要正确处理某些资源时。