Iterator 헬퍼로 배열 없이 이터레이터를 지연 처리하기

Iterator 헬퍼(ES2025)로 배열 변환 없이 map·filter·take를 지연 실행하는 방법과 주의사항

10/25/2026

javascript

조회수 1

문제 배경

Map이나 Set, 제너레이터에서 값을 꺼내 가공할 때 습관처럼 [...map.values()]나 Array.from()으로 먼저 배열을 만들고 filter, map을 이어 붙이곤 합니다. 데이터가 작으면 문제가 없지만, 이 방식에는 두 가지 한계가 있습니다.

  • 체인의 단계마다 중간 배열이 새로 만들어집니다. 처음 몇 개만 필요한 경우에도 전체를 끝까지 순회합니다.
  • 무한 시퀀스나 끝을 알 수 없는 제너레이터는 배열로 펼칠 수 없습니다.

ECMAScript 2025에 포함된 Iterator 헬퍼를 쓰면 배열로 바꾸지 않고 이터레이터 위에서 바로 map, filter, take 같은 연산을 이어 쓸 수 있습니다. 값은 필요한 만큼만, 하나씩 지연(lazy) 계산됩니다.

핵심 개념

헬퍼는 Iterator.prototype에 있다

헬퍼 메서드는 전역 Iterator의 prototype에 정의되어 있습니다. 배열 이터레이터(values(), keys(), entries()), Map·Set 이터레이터, 문자열 이터레이터, matchAll()의 결과, 제너레이터 객체가 모두 이 프로토타입을 상속하므로 별도 설정 없이 사용할 수 있습니다.

단, 이터러블 자체에는 헬퍼가 없습니다. 배열의 map은 원래 있던 Array.prototype.map이고, Map이나 Set 객체에는 filter가 없습니다. 먼저 .values() 등으로 이터레이터를 얻어야 합니다.

지연 메서드와 즉시 메서드

헬퍼는 새 이터레이터를 반환하는 지연 메서드와, 이터레이터를 소비해 최종 값을 반환하는 메서드로 나뉩니다.

구분메서드설명
지연(새 이터레이터 반환)map, filter, take, drop, flatMap순회가 시작되기 전까지 아무 일도 하지 않음
소비(값 반환)toArray, reduce, forEach이터레이터를 끝까지 소비
소비(조기 종료)some, every, find답이 정해지면 즉시 멈춤

여기에 이터레이터 비슷한 객체를 제대로 된 이터레이터로 감싸 주는 정적 메서드 Iterator.from()이 더해집니다.

실무 예제

무한 시퀀스에서 필요한 만큼만 꺼내기

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

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

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

naturals()는 끝나지 않는 제너레이터지만, take(3)이 세 개를 내보낸 뒤 순회를 종료하므로 toArray()가 정상적으로 반환됩니다. 배열이었다면 시작조차 할 수 없는 코드입니다.

Map을 배열로 펼치지 않고 가공하기

const stock = new Map([
  ['apple', 0],
  ['banana', 12],
  ['cherry', 5],
]);

const inStock = stock
  .entries()
  .filter(([, count]) => count > 0)
  .map(([name]) => name)
  .toArray();

console.log(inStock); // ['banana', 'cherry']

const total = stock.values().reduce((sum, count) => sum + count, 0);
console.log(total); // 17

const hasSoldOut = stock.values().some((count) => count === 0);
console.log(hasSoldOut); // true

기존에는 [...stock.entries()].filter(...).map(...)처럼 쓰면서 단계마다 배열을 만들었지만, 헬퍼를 쓰면 최종 결과가 필요한 toArray() 시점에만 배열이 하나 생깁니다.

페이지 단위 API를 지연 순회하기

제너레이터와 함께 쓰면 "앞에서부터 조건에 맞는 첫 항목만 찾기" 같은 로직에서 불필요한 계산을 줄일 수 있습니다. 아래 예제는 동기 데이터로 흉내 냈지만 구조는 같습니다.

const pages = [
  [{ id: 1, active: false }, { id: 2, active: false }],
  [{ id: 3, active: true }, { id: 4, active: true }],
  [{ id: 5, active: true }],
];

function* readPages() {
  for (const [index, page] of pages.entries()) {
    console.log(`page ${index} 읽음`);
    yield page;
  }
}

const firstActive = readPages()
  .flatMap((page) => page.values())
  .find((user) => user.active);

console.log(firstActive); // { id: 3, active: true }
// 출력: page 0 읽음, page 1 읽음 — 마지막 페이지는 읽지 않습니다.

find는 조건을 만족하는 값을 찾자마자 멈추고, 원본 이터레이터의 return()을 호출해 정리합니다. 그래서 제너레이터 안의 finally 블록도 정상적으로 실행됩니다.

Iterator.from으로 직접 만든 이터레이터 감싸기

const countdown = {
  current: 3,
  next() {
    if (this.current > 0) {
      return { value: this.current--, done: false };
    }
    return { value: undefined, done: true };
  },
};

console.log(Iterator.from(countdown).map((n) => `${n}초`).toArray());
// ['3초', '2초', '1초']

next()만 구현한 객체는 Iterator.prototype을 상속하지 않아 헬퍼가 없습니다. Iterator.from()으로 감싸면 헬퍼를 쓸 수 있는 이터레이터가 됩니다. 이미 Iterator.prototype을 상속한 이터레이터나 이터러블을 넘기면 해당 이터레이터를 그대로 사용합니다.

주의사항

이터레이터는 한 번만 소비된다

헬퍼가 반환하는 이터레이터는 원본과 상태를 공유합니다. 복사본이 아니므로 한쪽에서 값을 꺼내면 다른 쪽에서는 그 값을 볼 수 없습니다.

const source = [1, 2, 3].values();
const doubled = source.map((n) => n * 10);

console.log(source.next().value); // 1
console.log(doubled.next().value); // 20 — 1은 이미 소비됨

같은 데이터를 여러 번 순회해야 한다면 매번 .values()를 새로 호출하거나, 한 번 toArray()로 결과를 고정해 두세요.

무한 이터레이터에는 먼저 take

toArray, reduce, forEach는 이터레이터가 끝날 때까지 소비하므로 무한 시퀀스에서는 반환되지 않습니다. 반드시 take(n)이나 조기 종료 메서드(find, some, every)로 범위를 정해야 합니다.

인자 검증과 flatMap의 반환 값

  • take, drop에 음수나 NaN을 넘기면 RangeError가 발생합니다.
  • Array.prototype.flatMap과 달리, Iterator.prototype.flatMap의 콜백은 이터레이터나 이터러블 객체를 반환해야 합니다. 일반 값이나 문자열을 반환하면 TypeError가 발생합니다. 배열을 반환하는 것은 괜찮습니다.
  • 콜백에는 배열 메서드처럼 (value, index)가 전달되지만, 세 번째 인자(원본 배열)는 없습니다.

모든 Web API 이터레이터가 대상은 아니다

NodeList의 values()처럼 배열 이터레이터를 반환하는 경우에는 바로 쓸 수 있지만, 일부 Web API 객체는 자체 이터레이터 타입을 반환합니다. 헬퍼가 있는지 확실하지 않다면 Iterator.from()으로 감싸는 편이 안전합니다.

지원 환경

MDN 기준 Chrome 122, Firefox 131, Safari 18.4, Node.js 22, Deno 1.39부터 지원하며, 2025년 3월에 Baseline(Newly available)이 되었습니다. 구형 브라우저를 지원해야 한다면 core-js 같은 폴리필이 필요합니다. TypeScript는 5.6부터 IteratorObject 타입과 함께 헬퍼 타입을 제공하므로, tsconfig.json의 lib에 ESNext처럼 해당 타입을 포함하는 설정이 들어 있어야 합니다.

참고로 async 이터레이터용 헬퍼는 별도 제안(Async Iterator Helpers)으로 아직 표준이 아니므로, for await 대상에는 지금 소개한 메서드를 쓸 수 없습니다.

정리

  • Iterator 헬퍼는 ES2025에 포함되었고, 배열로 바꾸지 않고 이터레이터 위에서 map, filter, take, drop, flatMap을 지연 실행합니다.
  • toArray, reduce, forEach는 끝까지 소비하고, find, some, every는 답이 정해지면 멈춥니다.
  • Map·Set·배열은 .values() 등으로 이터레이터를 먼저 얻고, 직접 만든 이터레이터는 Iterator.from()으로 감쌉니다.
  • 이터레이터는 한 번만 소비된다는 점과, 무한 시퀀스에는 먼저 take를 걸어야 한다는 점을 기억하세요.

참고 자료: TC39 proposal-iterator-helpers, MDN Iterator

댓글 0