k8o

LLMS

Iterator.concat()で複数のIterableを1つのIteratorにまとめる

Iterator.concat()は複数のIterableを受け取り、それらの値を順番に返す新しいIteratorを生成する静的メソッドです。ジェネレータ関数やスプレッド構文による配列変換を使わずに、宣言的かつ遅延評価でIterableを結合できます。

公開: 2026年4月6日(月)
更新: 2026年4月6日(月)

はじめに

以前紹介したBaseline 2025に追加されたIterator Helpersでは、Iteratorオブジェクトに対してmapfiltertakedropなどの操作が可能になりました。

Baseline 2026では、これらに加えて複数のIterableを1つのIteratorにまとめるIterator.concat()が利用できるようになりました。

これまでの方法

複数のIterableの値を順番に処理したい場面はよくあります。これまでは、スプレッド構文で配列に変換してから結合するか、ジェネレータ関数でyield*を使う方法がありました。

// スプレッド構文で配列に変換する方法
const combined = [...[1, 2, 3], ...[4, 5, 6]];
// [1, 2, 3, 4, 5, 6]

スプレッド構文は簡潔ですが、すべての値を即座に配列へ展開するため、遅延評価の恩恵を受けられません。

// ジェネレータ関数を使う方法
function* concat(...iterables) {
  for (const iterable of iterables) {
    yield* iterable;
  }
}

const combined = concat([1, 2, 3], [4, 5, 6]);

ジェネレータ関数なら遅延評価は実現できますが、結合のためだけに専用のヘルパー関数を定義する必要があります。

Iterator.concat()を使えば、専用関数の定義なしにIterableを宣言的に結合できます。

Iterator.concat()

Iterator.concat()Iteratorコンストラクタの静的メソッドで、複数のIterableを受け取り、それらの値を順番に返す新しいIteratorオブジェクトを生成します。

const iter = Iterator.concat([1, 2, 3], [4, 5, 6]);
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: 4, done: false }
console.log(iter.next()); // { value: 5, done: false }
console.log(iter.next()); // { value: 6, done: false }
console.log(iter.next()); // { value: undefined, done: true }

引数には任意の数のIterableを渡せます。配列だけでなくSetMapなど、[Symbol.iterator]を持つオブジェクトを混在させて渡せます。

const array = [1, 2];
const set = new Set([3, 4]);
const map = new Map([['a', 5], ['b', 6]]);

const iter = Iterator.concat(array, set, map);

for (const value of iter) {
  console.log(value);
}
// 1 2 3 4 ['a', 5] ['b', 6]

Mapはそのまま渡すと[key, value]のペアが返ります。

遅延評価

Iterator.concat()が返すIteratorオブジェクトは遅延評価されます。結合した結果を配列にまとめるのではなく、next()が呼ばれたタイミングで値を取り出します。

function* generateNumbers(label, count) {
  for (let i = 0; i < count; i++) {
    console.log(`${label}: ${i}`);
    yield i;
  }
}

const iter = Iterator.concat(
  generateNumbers('A', 2),
  generateNumbers('B', 2),
);

// この時点ではまだ何も評価されていない

console.log(iter.next());
// A: 0
// { value: 0, done: false }

console.log(iter.next());
// A: 1
// { value: 1, done: false }

console.log(iter.next());
// B: 0
// { value: 0, done: false }

console.log(iter.next());
// B: 1
// { value: 1, done: false }

1つ目のIterableの値をすべて返し終えてから、2つ目のIterableの値を返し始めていることがわかります。必要になるまで各Iterableの値は評価されません。

なお、先頭に無限Iterableを渡すと後続のIterableには永遠に到達しないため、順序には注意が必要です。

引数はIterableであること

Iterator.concat()の引数はIterable[Symbol.iterator]を持つオブジェクト)である必要があります。配列、SetMap、文字列、ジェネレータオブジェクトなどの組み込みIterableはそのまま渡せます。

[Symbol.iterator]を持つオブジェクトであれば渡せますが、nextメソッドだけを持つ非Iterableなオブジェクトを渡すとTypeErrorになります。

// [Symbol.iterator]を持つオブジェクトはIterableなので渡せる
const iterable = {
  [Symbol.iterator]() {
    let count = 0;
    return {
      next() {
        if (count < 3) {
          return { value: count++, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  },
};
Iterator.concat(iterable); // OK

// nextメソッドだけを持つオブジェクトはIterableではない
const rawIterator = {
  count: 0,
  next() {
    if (this.count < 3) {
      return { value: this.count++, done: false };
    }
    return { value: undefined, done: true };
  },
};
Iterator.concat(rawIterator); // TypeError

Iterator.concat()は呼び出し時にすべての引数に対して[Symbol.iterator]メソッドの存在を検証し、反復中に途中終了が起きた場合は現在のイテレータを確実にクローズします。

終わりに

Iterator.concat()により、複数のIterableを結合するためにジェネレータ関数を自前で用意したり、配列に変換する必要がなくなりました。

Iterator Helpersmapfilterと組み合わせれば、複数のデータソースを遅延評価を活かしながら宣言的に処理できます。

0
もくじ