필요한 만큼만 일한다 — lazy, pull-based. array 메서드(filter/map/slice)는 매 단계 중간 배열을 새로 할당하고(eager), 10개만 필요해도 전부 처리한다. Iterator Helpers는 iterator 위에 붙는 chainable 메서드로 같은 체인을 lazy하게 돈다.

async function* fetchPages() {
  let page = 1
  while (true) {
    // 무한 루프지만 안전
    const res = await fetch(`/api/items?page=${page++}`)
    if (!res.ok) return
    yield* await res.json()
  }
}

const firstTen = await fetchPages().filter(isValid).take(10).toArray()

흐름은 소비자가 당기는 pull: toArray()가 값을 당기면 takefilterfetchPages()로 거슬러 올라가 한 개씩 요청한다. filter 탈락이면 그 자리서 다음 값을 다시 당기고(중간 배열 없음), take(N)이 N개를 채우는 순간 iterator가 종료된다 → 아직 안 본 페이지는 fetch 요청 자체가 안 나간다. while (true)가 안전한 이유.

멘탈 모델: iterator는 이미 가진 데이터가 아니라 *아직 일어나지 않은 작업(work)*이다.

요점:

  • iterator로 변환: .values() / .keys() / .entries() 또는 generator. slice(0,n)take(n), slice(n)drop(n).
  • reduce는 전체를 봐야 해서 eager하게 소비(예외).
  • RxJS가 push(값이 밀려옴 · 이벤트/시간)라면 이쪽은 pull(소비자가 당김) → 구조적으로 backpressure가 걸린다.

함정:

  • lazy 실행: toArray()/for await 전엔 side effect(fetch 포함)가 안 일어난다 → “왜 요청이 안 나가지?”
  • one-shot: 한 번 소비한 iterator는 재사용·random access(items[5]) 불가. console.log로 찍으면 값이 소모돼 동작이 바뀐다.

모던 브라우저 + Node 22+.


  • 출처: Matt Smith, “Stop turning everything into arrays (and do less work instead)” (2026.01)
#564