≪ Today I learned. RSS購読
公開日
タグ
JavaScript
著者
ダーシノ(@bc_rikko)

ES2025 Iterator Helpersを使う

2025年〜2026年のECMAScript界隈はイテレータ元年になりそうなほどIterator関連のProposalが承認された。

ECMAScript proposal updates@2024-10

2024年10月は以下のようなIterator関連のProposalsがアップデートされた

そのなかでES2025の仕様に追加されることになったIterator Helpersについて学ぶ。

Iterator Helpers

Iteratorをより使いやすくするためのメソッドが増えた。ほとんどがArrayが持つメソッドと同じようなものだが、Iteratorを使うことで遅延評価が可能になる。大規模なデータ群を扱うときなどに有用だ。

Iteratorを使うメリットについては末尾の「おまけ」参照

Iterator#map(fn)

Array#map()と同じ機能をイテレータに対して行うメソッド。

/**
 * 0から始まる自然数を生成するジェネレータ
 */
function* naturals() {
  let i = 0
  while (true) {
    yield i
    i += 1
  }
}

const result = naturals().map(x => x * x)

result.next() // { value: 0, done: false }
result.next() // { value: 1, done: false }
result.next() // { value: 4, done: false }

Iterator#filter(fn)

Array#filter()と同じ機能をイテレータに対して行うメソッド。

function* naturals() { ... }

const result = naturals().filter(x => x % 2 === 0)
result.next() // { value: 0, done: false }
result.next() // { value: 2, done: false }
result.next() // { value: 4, done: false }

Iterator#take(limit)

Array#slice(0, n)と同じ機能をイテレータに対して行うメソッド。

function* naturals() { ... }

const result = naturals().take(3)
result.next() // { value: 0, done: false }
result.next() // { value: 1, done: false }
result.next() // { value: 2, done: false }
result.next() // { value: undefined, done: true }

Iterator#drop(limit)

Array#slice(n)と同じ機能をイテレータに対して行うメソッド。

function* naturals() { ... }

const result = naturals().drop(3)
result.next() // { value: 3, done: false }
result.next() // { value: 4, done: false }
result.next() // { value: 5, done: false }

Iterator#flatMap(fn)

Array#flatMap()と同じ機能をイテレータに対して行うメソッド。

// Array#values()はイテレータオブジェクトを返す
const sunny = ["It's sunny in", "", "California"].values()

const result = sunny.flatMap(x => x.split(' ').values())
result.next() // { value: "It's", done: false }
result.next() // { value: "sunny", done: false }
result.next() // { value: "in", done: false }
result.next() // { value: "", done: false }
result.next() // { value: "California", done: false }
result.next() // { value: undefined, done: true }

Iterator#reduce(fn [, initialValue])

Array#reduce()と同じ機能をイテレータに対して行うメソッド。

function* naturals() { ... }

const result = naturals()
  .take(5)
  .reduce((sum, val) => sum + val, 3)

result // 13

Iterator#toArray()

イテレータを配列に変換するメソッド。

function* naturals() { ... }

const result = naturals()
  .take(5)
  .toArray()

result // [0, 1, 2, 3, 4]

Iterator#forEach(fn)

Array#forEach()と同じ機能をイテレータに対して行うメソッド。

const fn = val => console.log(val)
const items = [1, 2, 3].values()

items.forEach(fn)
// 1, 2, 3

Iterator#some(fn)

Array#some()と同じ機能をイテレータに対して行うメソッド。

function* naturals() { ... }

naturals().take(4).some(v => v > 1) // true

Iterator#every(fn)

Array#every()と同じ機能をイテレータに対して行うメソッド。

function* naturals() { ... }

naturals().take(4).every(v => v >= 0) // true

Iterator#find(fn)

Array#find()と同じ機能をイテレータに対して行うメソッド。

function* naturals() { ... }

naturals().find(v => v > 1) // 2

Iterator.from(object)

いままで紹介したメソッドとは異なるスタティックメソッドで、オブジェクトをイテレータでラップできる。

class Iter {
  next() {
    return { done: false, value: 1 }
  }
}

const iter = new Iter()
const wrapper = Iterator.from(iter)
wrapper.next() // { value: 1, done: false }

おまけ: Iteratorを使うメリット

Arrayを使う場合

const naturals = [1,2,3,4,5,6,7,8,9,10]

const array = naturals
  .filter(a => {
    console.log('filter:', a)
    return a % 2 === 0
  })
  .map(a => {
    console.log('map:', a)
    return a * a
  })
  .slice(0, 2)

console.log(array)
// console
[LOG]: "filter:",  1 
[LOG]: "filter:",  2 
[LOG]: "filter:",  3 
[LOG]: "filter:",  4 
[LOG]: "filter:",  5 // ← 使われない値
[LOG]: "filter:",  6 // ← 使われない値
[LOG]: "filter:",  7 // ← 使われない値
[LOG]: "filter:",  8 // ← 使われない値
[LOG]: "filter:",  9 // ← 使われない値
[LOG]: "filter:",  10 // ← 使われない値
[LOG]: "map:",  2 
[LOG]: "map:",  4 
[LOG]: "map:",  6 // ← 使われない値
[LOG]: "map:",  8 // ← 使われない値
[LOG]: "map:",  10// ← 使われない値 
[LOG]: [4, 16] 

Iteratorを使う場合

const naturals = [1,2,3,4,5,6,7,8,9,10]
const iterator = naturals
  .values()
  .filter(a => {
    console.log('filter:', a)
    return a % 2 === 0
  })
  .map(a => {
    console.log('map:', a)
    return a * a
  })
  .take(2)

console.log(iterator.next())
console.log(iterator.next())
// console
[LOG]: "filter:",  1 
[LOG]: "filter:",  2 
[LOG]: "map:",  2 
[LOG]: { "value": 4, "done": false } 
[LOG]: "filter:",  3 
[LOG]: "filter:",  4 
[LOG]: "map:",  4 
[LOG]: { "value": 16, "done": false } 

必要な分だけ実行されるため、大きなデータを扱うときに難しいことしなくても最適化できる。

ブラウザ対応状況