[G1-Sync] Manual knowledge update

This commit is contained in:
Antigravity Agent
2026-05-09 21:08:02 +09:00
parent f0befc887a
commit 93ec7e9056
363 changed files with 68333 additions and 64 deletions
@@ -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]]