ES2025 Iterator Helpersを使う
2025年〜2026年のECMAScript界隈はイテレータ元年になりそうなほどIterator関連のProposalが承認された。
ECMAScript proposal updates@2024-10
2024年10月は以下のようなIterator関連のProposalsがアップデートされた
- Iterator Helpers: stage3→4
- Iterator Sequencing: stage2→2.7
- Iterator Chunking: stage1→2
そのなかで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 }
必要な分だけ実行されるため、大きなデータを扱うときに難しいことしなくても最適化できる。