[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
---
|
||||
id: js-async-iterator-patterns
|
||||
title: JS Async Iterator — pull 기반 스트림 처리
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [javascript, typescript, async-iterator, streams, vibe-coding]
|
||||
tech_stack: { language: "TypeScript / Node.js / Browser", applicable_to: ["Backend", "Web"] }
|
||||
applied_in: []
|
||||
aliases: [for await, async generator, AsyncIterable, Symbol.asyncIterator]
|
||||
---
|
||||
|
||||
# Async Iterator — pull 기반 스트림
|
||||
|
||||
> Async generator (`async function*`) + `for await` 는 **pull 기반 자연스러운 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 스트리밍
|
||||
```ts
|
||||
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);
|
||||
}
|
||||
```
|
||||
|
||||
### 동시성 제한 매핑
|
||||
```ts
|
||||
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)
|
||||
```ts
|
||||
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
|
||||
```ts
|
||||
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<void>[] 반환 — 안 동작. for-await 필수.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- 페이지네이션 / 라인 처리 / streaming HTTP 는 디폴트 async generator.
|
||||
- AbortSignal 와 try/finally 한 쌍.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Backpressure_Patterns]]
|
||||
- [[Idempotent_Operations]]
|
||||
Reference in New Issue
Block a user