JavaScriptで遅延評価をしたい
RxJSやLazy.jsを利用すれば遅延評価によるmap()やfilter()を活用できたが、ES2025から公式に実装されたみたい。
具体的にはIterator Helpersで実装されていて、2025-10に決まった模様。
実装としては以下のように、Iteratorを通して処理を行うことで遅延処理になる。
code:javascript
const result = Iterator.from(arr)
.map((val) => val * val)
.map((val) => val + 1)
.filter((val) => val % 2 === 0)
.toArray();
この場合、arr内にある先頭の値から、一つずつ順番に map map filterを書けた結果を返す。
今まで書けたarr.map().map().filter()の場合は、全要素に対して、map を行った後、また全要素に対して map を行い、最後に filter を行う形となる。
Node.jsは22から実装
すでに主要ブラウザではサポート済み
時間計測をしてみる
検証環境:Chrome (Version 135.0.7049.85 (Official Build) (arm64)) のdevTools
イテレータの場合は複数の変換処理を1回のループで終わらせるため、Iteratorの処理が通常の処理よりも速いはず。
そう考え、JavaScriptにて計測コードを書いて測ってみた。
code:javascript
// 元の配列
const arr = Array.from({ length: 1000000 }, () =>
Math.floor(Math.random() * 100)
);
// 通常のmapとfilterを用いた処理
function arrayProcessing(arr) {
return arr
.map((val) => val * val)
.map((val) => val + 1)
.filter((val) => val % 2 === 0);
}
// イテレータヘルパーを用いた遅延評価処理
function iteratorProcessing(arr) {
return Iterator.from(arr)
.map((val) => val * val)
.map((val) => val + 1)
.filter((val) => val % 2 === 0)
.toArray();
}
// パフォーマンステスト
console.time('Array Processing');
const arrayResult = arrayProcessing(arr);
console.timeEnd('Array Processing');
console.time('Iterator Processing');
const iteratorResult = iteratorProcessing(arr);
console.timeEnd('Iterator Processing');
ログ結果
Array Processing: 50.712890625 ms
Iterator Processing: 55.01806640625 ms
最終的に配列で扱う場合、Iteratorの方が遅い結果となった。恐らく配列変換(toArray)で総なめするため遅くなっていると考えられる。
どこで遅くなっているのかの確認もしてみた。
code:javascript
// 大きなデータセット
const arr = Array.from({ length: 1000000 }, () =>
Math.floor(Math.random() * 100)
);
// 通常のmapとfilterを用いた処理
function arrayProcessing(arr) {
console.time('Array map *2');
const mapSquared = arr.map((val) => val * val);
console.timeEnd('Array map *2');
console.time('Array map +1');
const mapPlusOne = mapSquared.map((val) => val + 1);
console.timeEnd('Array map +1');
console.time('Array filter');
const filtered = mapPlusOne.filter((val) => val % 2 === 0);
console.timeEnd('Array filter');
return filtered;
}
// イテレータヘルパーを用いた遅延評価処理
function iteratorProcessing(arr) {
console.time('Iterator map *2');
const iterator1 = Iterator.from(arr).map((val) => val * val);
console.timeEnd('Iterator map *2');
console.time('Iterator map +1');
const iterator2 = iterator1.map((val) => val + 1);
console.timeEnd('Iterator map +1');
console.time('Iterator filter');
const iterator3 = iterator2.filter((val) => val % 2 === 0);
console.timeEnd('Iterator filter');
console.time('Iterator toArray');
const result = iterator3.toArray();
console.timeEnd('Iterator toArray');
return result;
}
// パフォーマンステスト
console.time('Array Processing Total');
const arrayResult = arrayProcessing(arr);
console.timeEnd('Array Processing Total');
console.time('Iterator Processing Total');
const iteratorResult = iteratorProcessing(arr);
console.timeEnd('Iterator Processing Total');
実行結果
table: Performance Comparison
Task Array Time (ms) Iterator Time (ms)
map *2 10.77001953125 0.00390625
map +1 9.671142578125 0.0009765625
filter 27.635009765625 0.0009765625
toArray 60.497802734375
Processing Total 48.484130859375 60.722900390625
想定通りtoArray()での変換がほぼすべての時間を占めていることがわかった。また、途中の変換処理は1回のループで終わらせることもあり、爆速であることがわかった。なので、Iteratorを使うときはtoArray()で変換するのではなく、Iteratorをfor-of等で回して、最終結果を保持/出力するのが速い。
上記結果を考えてみると、複数の置換処理がある場合において、ある一定の配列量まで(toArray()が許容できる配列量まで)はIteratorの方が速く変換できそうである。こちらも確認してみた。
code:javascript
// 通常のmapとfilterを用いた処理
function arrayProcessing(arr) {
return arr
.map((val) => val * val)
.map((val) => val + 1)
.filter((val) => val % 2 === 0);
}
// イテレータヘルパーを用いた遅延評価処理
function iteratorProcessing(arr) {
return Iterator.from(arr)
.map((val) => val * val)
.map((val) => val + 1)
.filter((val) => val % 2 === 0)
.toArray();
}
// 元の配列
for (let exp = 0; exp <= 8; exp++) {
const size = Math.pow(10, exp);
const arr = Array.from({ length: size }, () =>
Math.floor(Math.random() * 100)
);
console.log(Array Size: ${size});
// パフォーマンステスト
console.time('Array Processing');
const arrayResult = arrayProcessing(arr);
console.timeEnd('Array Processing');
console.time('Iterator Processing');
const iteratorResult = iteratorProcessing(arr);
console.timeEnd('Iterator Processing');
console.log('--------------------------');
}
実行結果
code: text
Array Size: 1
Array Processing: 0.0478515625 ms
Iterator Processing: 0.06396484375 ms
--------------------------
Array Size: 10
Array Processing: 0.010986328125 ms
Iterator Processing: 0.02197265625 ms
--------------------------
Array Size: 100
Array Processing: 0.02001953125 ms
Iterator Processing: 0.024169921875 ms
--------------------------
Array Size: 1000
Array Processing: 0.35595703125 ms
Iterator Processing: 0.22705078125 ms
--------------------------
Array Size: 10000
Array Processing: 0.658935546875 ms
Iterator Processing: 3.567138671875 ms
--------------------------
Array Size: 100000
Array Processing: 21.60693359375 ms
Iterator Processing: 20.72216796875 ms
--------------------------
Array Size: 1000000
Array Processing: 41.27001953125 ms
Iterator Processing: 86.093017578125 ms
--------------------------
Array Size: 10000000
Array Processing: 339.73193359375 ms
Iterator Processing: 659.009033203125 ms
--------------------------
Array Size: 100000000
Array Processing: 3339.60791015625 ms
Iterator Processing: 6075.01513671875 ms
--------------------------
整理してみる。
table: result
Array Size Array Processing Time (ms) Iterator Processing Time (ms)
1 0.048 0.064
10 0.011 0.022
100 0.020 0.024
1,000 0.356 0.227
10,000 0.659 3.567
100,000 21.607 20.722
1,000,000 41.270 86.093
10,000,000 339.732 659.009
100,000,000 3339.608 6075.015
(少数第4位を四捨五入)
検証結果としては、1,000, 100,000個のとき以外は通常の処理を使ったほうが良い感じになりました。
遅延評価したほうが良いと思っていましたが、現状最終的に配列で扱うのであれば、普通に配列で処理する方が良さそうです。
最後に、toArray()が遅いのであれば、自分でforを回してみたらどうなるのかの検証もしました。
table: self toArray
Array Size Array Processing Time (ms) Iterator Processing Time (ms)
1 0.083 1.210
10 0.018 0.021
100 0.031 0.431
1,000 0.296 0.337
10,000 0.888 4.391
100,000 13.401 34.681
1,000,000 41.732 80.080
10,000,000 338.550 631.280
100,000,000 3236.156 5239.506
コードは長くなるので、変更した関数のみ掲載。
code: javascript
// イテレータヘルパーを用いた遅延評価処理
function iteratorProcessing(arr) {
const iterator = Iterator.from(arr)
.map(val => val * val)
.map(val => val + 1)
.filter(val => val % 2 === 0);
const result = [];
for (const val of iterator) {
result.push(val);
}
return result;
}
結果が違うことから、toArray()での最適化は一応行われているみたい。
for-ofが遅い可能性も考慮して、whileループでも測定してみた。
code: javascript
// イテレータヘルパーを用いた遅延評価処理
function iteratorProcessing(arr) {
const iterator = Iterator.from(arr)
.map(val => val * val)
.map(val => val + 1)
.filter(val => val % 2 === 0);
const result = [];
while(true) {
const next = iterator.next();
if (next.done) {
break;
}
result.push(iterator.next().value)
}
return result;
}
table: self toArray(while)
Array Size Array Processing Time (ms) Iterator Processing Time (ms)
1 0.075 0.114
10 0.009 0.013
100 0.021 0.044
1,000 0.273 0.308
10,000 1.089 2.524
100,000 21.142 18.541
1,000,000 42.111 57.737
10,000,000 364.151 571.561
100,000,000 3246.623 5075.712
やや速度改善はされましたが、結局通常の配列処理を超えることはなかったです。
まとめ
全配列に対しての何かしらの処理をする場合かつ、変換要素が3個ぐらいである場合は通常のmap, filterを活用したほうが良い。
最終的に先頭数個をsplitして取り出す処理など、最終結果で要素が絞られるものに関してはtoArray()によるデメリットが低くなるため、検討すると速度改善につながると思われる。