如何确定 javascript 迭代器是否提前终止?

Mei*_*hes 2 javascript iterator generator typescript async-iterator

假设我有一个发电机:

function* source() {
  yield "hello"; yield "world";
}
Run Code Online (Sandbox Code Playgroud)

我创建了可迭代对象,使用 for 循环进行迭代,然后在迭代器完全完成之前退出循环(返回完成)。

function run() {
  for (let item of source()) {
    console.log(item);
    break;
  }
}
Run Code Online (Sandbox Code Playgroud)

问题:如何从可迭代端发现迭代器提前终止?

如果您尝试直接在生成器本身中执行此操作,似乎没有任何反馈:

function* source2() {
  try {
    let result = yield "hello";
    console.log("foo");
  } catch (err) {
    console.log("bar");
  }
}
Run Code Online (Sandbox Code Playgroud)

...既没有记录“foo”也没有记录“bar”。

Mei*_*hes 6

我注意到打字稿将Iterator(lib.es2015)定义为:

interface Iterator<T> {
  next(value?: any): IteratorResult<T>;
  return?(value?: any): IteratorResult<T>;
  throw?(e?: any): IteratorResult<T>;
} 
Run Code Online (Sandbox Code Playgroud)

我拦截了这些方法并记录了调用,并且看起来如果迭代器提前终止for-loop——至少通过一个——然后return调用该方法。如果消费者抛出错误,它也会被调用。如果循环被允许完全迭代的迭代器return叫。

Return 黑客

所以,我做了一些 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;
}
Run Code Online (Sandbox Code Playgroud)

它有效!

你好

删除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;
}
Run Code Online (Sandbox Code Playgroud)

成功!

你好
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
}
Run Code Online (Sandbox Code Playgroud)

你好
FooBar!

包裹

我已将上述解决方案包装为一个包。它同时支持[Symbol.iterator][Symbol.asyncIterator]。我对异步迭代器的情况特别感兴趣,尤其是当某些资源需要正确处理时。


flu*_*ggo 6

有一种更简单的方法可以做到这一点:使用finally 块。

function *source() {
  let i;

  try {
    for(i = 0; i < 5; i++)
      yield i;
  }
  finally {
    if(i !== 5)
      console.log('  terminated early');
  }
}

console.log('First:')

for(const val of source()) {
  console.log(`  ${val}`);
}

console.log('Second:')

for(const val of source()) {
  console.log(`  ${val}`);

  if(val > 2)
    break;
}
Run Code Online (Sandbox Code Playgroud)

...产量:

First:
  0
  1
  2
  3
  4
Second:
  0
  1
  2
  3
  terminated early
Run Code Online (Sandbox Code Playgroud)