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

    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 } 

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

    ブラウザ対応状況