Nem minden .map() ugyanaz: az Iterator helperek lustán dolgoznak a JavaScriptben

Az Iterator.prototype 2025 óta saját map/filter/take metódusokkal rendelkezik, ami a klasszikus tömbmetódusoktól eltérően lusta kiértékeléssel, köztes tömb nélkül dolgozik – akár végtelen generátorokon is.

A .map() és a .filter() évek óta ott van a tömbjeidben, de 2025 óta már nem csak a tömböké. Az Iterator objektumnak saját prototípus-metódusai lettek: map, filter, take, drop, reduce, toArray és még pár. Ugyanazok a nevek, de más a viselkedésük – és ez a különbség pont ott számít, ahol a klasszikus tömbmetódusok fájnak: nagy adathalmazoknál és végtelen sorozatoknál.

Diagram: felül a klasszikus tömbmetódusok minden lépésben teljes köztes tömböt hoznak létre, alul az Iterator helperek egy vékony csövön, elemenként, tárolás nélkül dolgoznak

Mi pontosan az Iterator helpers

A TC39 Iterator Helpers javaslat Stage 4-re jutott, és a finished-proposals lista szerint 2025-ben zárult le a specifikációba. A lényege egyszerű: az Iterator.prototype-ra felkerültek azok a metódusok, amiket eddig csak a Array.prototype-on ismertünk – map, filter, take, drop, flatMap, reduce, toArray, forEach, some, every, find –, plusz egy Iterator.from() statikus metódus, amivel bármilyen, csak next()-et implementáló objektumot fel lehet öltöztetni ezekkel a képességekkel.

Böngészőtámogatás MDN szerint: Chrome/Edge 122 (2024. február), Firefox 131 (2024. október), Safari 18.4 (2025. március), Node.js pedig 22.0.0 óta. 2026-ban ez már minden nagy motoron elérhető, tehát nem egy Chrome-only kuriózumról van szó. A Node.js oldalán a nodejs/node#51362 PR vezette be az új globális Iterator kezelését a v22-es fősorban.

A lényeg: lusta kiértékelés

A [1,2,3].map(f) azonnal lefut, és egy teljes új tömböt ad vissza. Az Iterator helperek ehelyett egy másik iterátort adnak vissza – a tényleges munka csak akkor történik meg, amikor valaki elkéri a következő elemet. Ez lehetővé teszi, hogy végtelen sorozatokon dolgozzunk anélkül, hogy a program felakadna:

function* naturalNumbers() {
  let n = 1;
  while (true) yield n++;
}

const firstThreeEvenSquares = naturalNumbers()
  .map(n => n * n)
  .filter(n => n % 2 === 0)
  .take(3)
  .toArray();

console.log(firstThreeEvenSquares); // [ 4, 16, 36 ]

A naturalNumbers() egy sosem leálló generátor. Ha ezt egy sima Array.from()-mal vagy [...naturalNumbers()]-lel próbálnád tömbbé alakítani, a program lefagyna – nem lenne hol megállnia. A .take(3) viszont pontosan azt garantálja, hogy a lánc csak addig kér elemet a generátortól, amíg három találat össze nem gyűlik, aztán leáll. Ez a fajta korlátozott, igény szerinti feldolgozás korábban csak kézzel írt ciklussal volt egyszerűen megoldható.

A gyakori félreértés: arr.map() nem ez

Ez a legfontosabb csapda. A Array.prototype.map nem lett kicserélve – [1,2,3].map(f) továbbra is a régi, azonnal kiértékelő metódus, és tömböt ad vissza, nem iterátort. Az új, lusta viselkedést csak akkor kapod meg, ha explicit iterátort kérsz a tömbtől, például a .values()-szel:

const numbers = [1, 2, 3, 4, 5, 6, 7, 8];

// Klasszikus: minden lépés teljes új tömböt hoz létre
const classic = numbers.filter(n => n % 2 === 0).map(n => n * 10);

// Iterator helper: egyetlen menetben, köztes tömb nélkül
const lazy = numbers.values().filter(n => n % 2 === 0).map(n => n * 10).toArray();

console.log(classic); // [ 20, 40, 60, 80 ]
console.log(lazy);    // [ 20, 40, 60, 80 ] – ugyanaz az eredmény

console.log(Array.isArray(numbers.map(n => n)));            // true – a régi metódus
console.log(numbers.values().map(n => n).constructor.name); // 'Iterator' – az új

A kettő ugyanazt az eredményt adja, de a klasszikus verzió a filter-nél lefoglal egy teljes köztes tömböt, amit aztán a map egy másik tömbre alakít át. Nyolc elemnél ez nem számít, de egy több tízezer soros feldolgozási láncban ez felesleges memóriaforgalom és garbage collector-terhelés – főleg, ha a lánc négy-öt lépésből áll.

Nem csak generátoron: bármin, ami iterálható

Az Iterator helperek ott jelennek meg, ahol iterátorral dolgozol – ez a Map és a Set beépített iterátoraira is igaz:

const cache = new Map([['a', 1], ['b', 2], ['c', 3]]);

console.log(cache.entries().filter(([, v]) => v > 1).toArray());
// [ [ 'b', 2 ], [ 'c', 3 ] ]

Ha viszont saját, kézzel írt iterátorod van – egy objektum, ami csak a next()-et implementálja, de nem örököl az Iterator.prototype-tól –, az Iterator.from() öltözteti fel a helperekkel:

function createRange(start, end) {
  let current = start;
  return {
    next() {
      if (current > end) return { done: true, value: undefined };
      return { done: false, value: current++ };
    },
  };
}

const range = Iterator.from(createRange(1, 10));
console.log(range.filter(n => n % 3 === 0).toArray()); // [ 3, 6, 9 ]

Ez különösen hasznos, ha egy régebbi API vagy saját osztály csak a minimális iterátorprotokollt implementálja – nem kell átírni, elég egyszer becsomagolni.

Ahol elakadhatsz

  • A reduce() kezdőérték nélkül ugyanúgy hibát dob, mint a Array.prototype.reduce: TypeError: Reduce of a done iterator with no initial value, ha az iterátor üres.
  • Egy iterátor csak egyszer fogyasztható el. Ha a lánc végén kétszer hívod a .toArray()-t (vagy előbb .some()-mal, utána .toArray()-vel járod körbe), a második hívás már kiürült iterátort kap.
  • Régebbi böngészőt vagy Node-ot (a 2024 előtti verziókat) még ki kell zárni, vagy a core-js core-js/proposals/iterator-helpers polyfillját kell bevonni – natív implementáció nélkül a .values().map() simán TypeError-ral bukik el.

A gyakorlatban ott érdemes bevetni, ahol tényleg sok elemen és több egymásra épülő lépésen kell végigmenni – log-sorok szűrése és transzformálása, egy nagy Map-ből kiválasztott rekordok feldolgozása, vagy bármi, ahol eddig kézzel írt for...of ciklusba kényszerültél, mert a klasszikus tömbmetódusok korai megállítása (a mai .take() hiányában) nem volt kényelmesen kifejezhető. Kis, egyszeri átalakításnál a klasszikus .filter().map() továbbra is teljesen rendben van – a lustaság ára egy plusz mentális modell, amit nem minden háromelemű tömbnél kell bevállalni.

Források

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük