JavaScript Iterator Helpers入門

JavaScriptで大規模なデータセットを処理しようとしたことがある方なら、その苦労をご存知でしょう。.map()
や.filter()
といった従来の配列メソッドは、すべてのデータを一度にメモリに読み込むことを強制します。100万件のレコードや無限のデータストリームでこれを試すと、アプリケーションがクラッシュしてしまいます。JavaScript iterator helpersは、言語のコアに遅延評価を導入することで、この問題を解決します。
この記事では、新しいiterator helperメソッドの使い方を説明し、そのパフォーマンス上の利点を理解し、大きなファイルの処理、APIストリームの処理、無限シーケンスの操作といった実際のシナリオに適用する方法を示します。
重要なポイント
- Iterator helpersは、メモリ効率的なデータ処理のための遅延評価を提供します
.values()
で配列を変換し、Iterator.from()
で他のイテラブルを変換します.map()
、.filter()
、.take()
などのメソッドは、中間配列を作成せずにチェーンできます- 無限シーケンス、大きなファイル、ストリーミングデータに最適です
- 単回使用のみ - 複数回の反復には新しいイテレータを作成してください
Iteratorプロトコルの理解
新しいhelpersに入る前に、イテレータが特別である理由を明確にしましょう。イテレータは単純に、{value, done}
のペアを返す.next()
メソッドを持つオブジェクトです:
const iterator = {
current: 0,
next() {
return this.current < 3
? { value: this.current++, done: false }
: { done: true }
}
}
console.log(iterator.next()) // { value: 0, done: false }
console.log(iterator.next()) // { value: 1, done: false }
配列、Set、Map、ジェネレータはすべて、[Symbol.iterator]()
メソッドを通じてイテレータプロトコルを実装しています。このプロトコルはfor...of
ループやスプレッド演算子を動かしますが、最近まで、イテレータには開発者が期待する関数型プログラミングメソッドが不足していました。
JavaScript Iterator Helpers:新機能
Iterator helpersは、配列操作を模倣しつつ遅延的に動作するメソッドでIteratorプロトタイプを拡張します:
メソッド | 説明 | 戻り値 |
---|---|---|
.map(fn) | 各値を変換 | Iterator |
.filter(fn) | テストに合格した値を生成 | Iterator |
.take(n) | 最初のn個の値を生成 | Iterator |
.drop(n) | 最初のn個の値をスキップ | Iterator |
.flatMap(fn) | マップして結果を平坦化 | Iterator |
.reduce(fn, init) | 単一の値に集約 | Value |
.find(fn) | テストに合格した最初の値 | Value |
.some(fn) | いずれかの値がテストに合格するかテスト | Boolean |
.every(fn) | すべての値がテストに合格するかテスト | Boolean |
.toArray() | すべての値を収集 | Array |
これらのメソッドを使用するには、まずデータ構造をイテレータに変換します:
// 配列の場合
const result = [1, 2, 3, 4, 5]
.values() // イテレータに変換
.filter(x => x % 2 === 0)
.map(x => x * 2)
.toArray() // [4, 8]
// 他のイテラブルの場合
const set = new Set([1, 2, 3])
const doubled = Iterator.from(set)
.map(x => x * 2)
.toArray() // [2, 4, 6]
遅延評価vs即座評価:重要な違い
従来の配列メソッドはすべてを即座に処理します:
// 即座評価 - すべての要素をすぐに処理
const eager = [1, 2, 3, 4, 5]
.map(x => {
console.log(`Mapping ${x}`)
return x * 2
})
.filter(x => x > 5)
// ログ出力: Mapping 1, 2, 3, 4, 5
// 結果: [6, 8, 10]
Iterator helpersは消費されるときのみ値を処理します:
// 遅延評価 - 必要な分だけ処理
const lazy = [1, 2, 3, 4, 5]
.values()
.map(x => {
console.log(`Mapping ${x}`)
return x * 2
})
.filter(x => x > 5)
.take(2)
// まだ何もログ出力されない!
const result = [...lazy]
// ログ出力: Mapping 1, 2, 3
// 結果: [6, 8]
遅延評価版が2つの一致する値を見つけた後で停止し、要素4と5を処理しないことに注目してください。この効率性は、大きなデータセットを扱う際に重要になります。
実用的な例と使用例
大きなファイルを行ごとに処理
ファイル全体をメモリに読み込む代わりに:
async function* readLines(file) {
const reader = file.stream().getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop()
for (const line of lines) yield line
}
if (buffer) yield buffer
}
// ファイル全体を読み込まずにCSVを処理
const validRecords = await readLines(csvFile)
.drop(1) // ヘッダーをスキップ
.map(line => line.split(','))
.filter(cols => cols[2] === 'active')
.take(100)
.toArray()
無限シーケンスの操作
無限データストリームを生成・処理:
function* fibonacci() {
let [a, b] = [0, 1]
while (true) {
yield a
;[a, b] = [b, a + b]
}
}
// 1000を超える最初のフィボナッチ数を見つける
const firstLarge = fibonacci()
.find(n => n > 1000) // 1597
// 最初の10個の偶数フィボナッチ数を取得
const evenFibs = fibonacci()
.filter(n => n % 2 === 0)
.take(10)
.toArray()
メモリ肥大化なしのAPIページネーション
ページ分割されたAPIを効率的に処理:
async function* fetchAllUsers(apiUrl) {
let page = 1
while (true) {
const response = await fetch(`${apiUrl}?page=${page}`)
const { data, hasMore } = await response.json()
for (const user of data) yield user
if (!hasMore) break
page++
}
}
// すべてのページを読み込まずにユーザーを処理
const premiumUsers = await fetchAllUsers('/api/users')
.filter(user => user.subscription === 'premium')
.map(user => ({ id: user.id, email: user.email }))
.take(50)
.toArray()
パフォーマンスの考慮事項とメモリ使用量
Iterator helpersが優れている場面:
- 利用可能メモリより大きなデータの処理
- 結果の一部のみが必要な場合
- 複数の変換をチェーンする場合
- ストリームやリアルタイムデータの操作
適さない場面:
- 要素へのランダムアクセスが必要な場合
- データセットが小さく既にメモリに収まっている場合
- 複数回の反復が必要な場合(イテレータは単回使用)
メモリ比較の例:
// メモリ集約的な配列アプローチ
function processLargeDataArray(data) {
return data
.map(transform) // 新しい配列を作成
.filter(condition) // 別の配列を作成
.slice(0, 100) // 3番目の配列を作成
}
// メモリ効率的なイテレータアプローチ
function processLargeDataIterator(data) {
return data
.values()
.map(transform) // 中間配列なし
.filter(condition) // 中間配列なし
.take(100)
.toArray() // 最終的な100項目のみメモリに
}
ブラウザサポートとポリフィル
JavaScript iterator helpersは以下でサポートされています:
- Chrome 122+
- Firefox 131+
- Safari 18.4+
- Node.js 22+
古い環境では、es-iterator-helpersポリフィルを使用してください:
npm install es-iterator-helpers
よくある落とし穴と解決策
イテレータは単回使用
const iter = [1, 2, 3].values().map(x => x * 2)
console.log([...iter]) // [2, 4, 6]
console.log([...iter]) // [] - 既に消費済み!
// 解決策:新しいイテレータを作成
const makeIter = () => [1, 2, 3].values().map(x => x * 2)
IteratorメソッドとArrayメソッドの混在
// 動作しない - filterはイテレータを返し、配列ではない
const result = [1, 2, 3]
.values()
.filter(x => x > 1)
.includes(2) // エラー!
// 解決策:まず配列に変換
const result = [1, 2, 3]
.values()
.filter(x => x > 1)
.toArray()
.includes(2) // true
まとめ
JavaScript iterator helpersは関数型プログラミングを遅延評価に導入し、大きなデータセットや無限データセットの効率的な処理を可能にします。.values()
やIterator.from()
をいつ使うか、遅延評価が即座配列メソッドとどう異なるかを理解することで、スケールするメモリ効率的なコードを書くことができます。ストリーミングデータ、ページネーション、すべてをメモリに読み込むことが現実的でないあらゆるシナリオで、これらのメソッドを使い始めましょう。
FAQ
標準のiterator helpersは同期イテレータでのみ動作します。非同期操作には、async iterator helpers(将来のESバージョンで提案予定)を待つか、非同期反復サポートを提供するライブラリを使用する必要があります。
Iterator helpersは言語に組み込まれた基本的な遅延評価を提供し、RxJSはエラーハンドリング、バックプレッシャー、複雑な演算子などの高度な機能を提供します。シンプルな変換にはiterator helpersを、複雑なリアクティブプログラミングにはRxJSを使用してください。
いいえ、配列メソッドはメモリに収まる小さなデータセットや、ランダムアクセスや複数回の反復が必要な場合に最適な選択肢のままです。Iterator helpersは大きなデータや無限データを含む特定の使用例で配列を補完します。
はい、Iteratorクラスを拡張するか、イテレータプロトコルを実装するカスタムオブジェクトで`Iterator.from()`を使用してください。これにより、組み込みhelpersとの互換性を維持しながら、ドメイン固有の変換を追加できます。