Files
2nd/10_Wiki/Topics/Coding/JS_Async_Iterator_Patterns.md
T
2026-05-09 21:08:02 +09:00

3.8 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
js-async-iterator-patterns JS Async Iterator — pull 기반 스트림 처리 Coding draft B conceptual 2026-05-09 2026-05-09
javascript
typescript
async-iterator
streams
vibe-coding
language applicable_to
TypeScript / Node.js / Browser
Backend
Web
for await
async generator
AsyncIterable
Symbol.asyncIterator

Async Iterator — pull 기반 스트림

Async generator (async function*) + for awaitpull 기반 자연스러운 backpressure 제공. consumer 가 await 끝낼 때까지 producer 가 다음 yield 안 함. RxJS 같은 push 모델보다 단순.

📖 핵심 개념

  • async function* gen() → 호출하면 AsyncGenerator.
  • for await (const x of gen()) 로 소비.
  • yield 시점에 consumer 가 await 중이면 producer suspend.
  • 자연스러운 cleanup: try/finally 가 break/throw 시에도 실행.

💻 코드 패턴

페이지 fetch 스트리밍

async function* paginate<T>(url: string): AsyncGenerator<T> {
  let cursor: string | null = null;
  do {
    const res = await fetch(`${url}?cursor=${cursor ?? ''}`).then(r => r.json());
    for (const item of res.items) yield item;
    cursor = res.nextCursor;
  } while (cursor);
}

// 소비자가 일찍 멈출 수 있음
for await (const user of paginate<User>('/api/users')) {
  if (await shouldStop(user)) break; // generator 의 finally 자동 실행
  await process(user);
}

동시성 제한 매핑

async function* mapConcurrent<T, U>(
  items: AsyncIterable<T>,
  fn: (t: T) => Promise<U>,
  concurrency: number
): AsyncGenerator<U> {
  const buffer: Promise<U>[] = [];
  for await (const item of items) {
    buffer.push(fn(item));
    if (buffer.length >= concurrency) {
      yield await buffer.shift()!;
    }
  }
  while (buffer.length) yield await buffer.shift()!;
}

파일 라인 단위 처리 (Node)

import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline/promises';

async function* lines(path: string): AsyncGenerator<string> {
  const rl = createInterface({ input: createReadStream(path), crlfDelay: Infinity });
  try {
    for await (const line of rl) yield line;
  } finally {
    rl.close();
  }
}

Cleanup with try/finally

async function* watch(): AsyncGenerator<Event> {
  const ctrl = new AbortController();
  try {
    for await (const ev of subscribe(ctrl.signal)) yield ev;
  } finally {
    ctrl.abort(); // break / throw 시 자동
  }
}

🤔 의사결정 기준

데이터 권장
한 번에 다 메모리에 못 담음 async iterator
페이지네이션 / 무한 스크롤 source async iterator
한 번 producer 시작 후 다수 consumer async iterator X — EventEmitter / SharedFlow
HTTP streaming response ReadableStream → async iterator (for await (const chunk of res.body))
RxJS 가 더 자연스러운 (combine, debounce, retry) RxJS

안티패턴

  • 소비 중 break 후 cleanup 안 함: try/finally 누락 → connection / file leak.
  • Promise.all 로 모두 모은 후 yield: pull 의 의미 없음. 메모리 폭발.
  • await 없는 yield: producer 가 즉시 다음 yield → backpressure 의미 잃음.
  • async iterator 안에서 setState 직접: React 컴포넌트면 unmount 후 위험. AbortSignal 결합.
  • iterator 이중 소비: 대부분 single-use. tee / branching 라이브러리 필요.
  • for-of 로 async iterator 소비: 그냥 Promise[] 반환 — 안 동작. for-await 필수.

🤖 LLM 활용 힌트

  • 페이지네이션 / 라인 처리 / streaming HTTP 는 디폴트 async generator.
  • AbortSignal 와 try/finally 한 쌍.

🔗 관련 문서