🎄

ENCA 4日目: Iterator Helpers で引数のバリデーションエラー時にイテレーターを閉じる(進行中)

2024/12/04に公開

イテレーターの return メソッド

昨日の記事でも少し触れていますが、イテレーターにはオプショナルな return メソッドが定義されています。これは仕様では IteratorClose 内で呼ばれ、イテレーターを閉じます。

interface Iterator {
  next(value?: any): IteratorResult;
  return?(value?: any): IteratorResult;
  throw?(error?: any): IteratorResult;
}

interface IteratorResult {
  done: boolean;
  value: any;
}

例えば for...of のループの途中で break, return, throw 文を使って抜けた際にイテレーターの return メソッドが呼ばれます。

const iterable = {
  [Symbol.iterator]() {
    let counter = 0;
    let isClosed = false;
    return {
      next() {
        if (isClosed) {
          return { done: true, value: undefined };
        }
        if (counter >= 10) {
          isClosed = true;
          return { done: true, value: undefined };
        }
        return { done: false, value: counter++ };
      },
      return() {
        isClosed = true;
        console.log("closed");
        return { done: true, value: undefined };
      },
    };
  },
};

for (const value of iterable) {
  if (value === 5) {
    break; // "closed"
  }
}

なお余談ですがイテレーターの throw メソッドはジェネレーター函数に例外が起きたことを通知するために用いられます。詳しくは Masaki Hara さんの記事が参考になります。

https://zenn.dev/qnighy/articles/112af47edfda96

ビルトインイテレーターの return メソッド

ECMAScript のビルトインイテレーターにおいて、ジェネレーター以外には return メソッドが実装されていません[1]。つまりジェネレーター函数を使う場合や、自身でカスタムイテレーターを実装する場合以外ではあまり閉じられることを考慮しなくても大丈夫です。

このあたりについては uhyo さんの記事が詳しいです。

https://zenn.dev/uhyo/articles/iterator-helpers-iterator-close

Iterator Helpers のメソッドで、引数のバリデーションエラー時にイテレーターを閉じる

現状 ES2025 Iterator Helpers で追加されたメソッドでは、与えられた引数のバリデーションエラー時にイテレーターが閉じられません。この挙動は一貫性に欠けると Iterator Sequencing の提案で core-js の作者 zloirock さんから指摘がなされ、PR が作られました。

https://github.com/tc39/ecma262/pull/3467

PR にも書かれていますが ES2025 Set Methods などで、内部でイテレーターを取得し、使用している箇所でちゃんと閉じていることからも、この変更は妥当性があるかなと思います。

(追記)ちょうど今開催中である2024年12月の会議で承認されました。

脚注
  1. 正確には ES2025 Iterator Helpers のメソッドの %IteratorHelperPrototype%Iterator.from でラップする %WrapForValidIteratorPrototype% でも定義されています。しかしこれらは元のイテレーターが持つ return メソッドを呼び出すためのもので、元のイテレーターで実装されていなければ何もしません。 ↩︎

Discussion