---
type: snippet
tags: ['react', 'hooks', 'async-generator', 'pattern', 'typescript', 'xstate']
status: release
ctime: 2026-05-25
mtime: 2026-05-25
---

여러 단계의 비동기 UI 업데이트를 `setLoading → setProgress → setLoading(false)`로 흩뿌리지 않고 async generator의 `yield` 시퀀스로 시간 순서대로 표현. 원본 트릭은 [@ericclemmons](https://x.com/ericclemmons/status/2055085109573734784).

```ts
type FlowEvent<P extends string, D> = { phase: P; data: D }

async function* loadingFlow(
  signal: AbortSignal,
): AsyncGenerator<FlowEvent<'starting' | 'slow' | 'done', string>> {
  yield { phase: 'starting', data: 'Starting…' }
  await wait(1000, signal)
  yield { phase: 'slow', data: 'Taking longer than usual' }
  await wait(2000, signal)
  yield { phase: 'done', data: 'Got it 🎉' }
}
```

## 원본의 빈 자리 → 소비 레이어 책임

원본 데모는 4가지가 비어 있음. hook이든 actor든 wrapping 레이어에서 채워야 함:

- **cancellation**: generator에 `AbortSignal` 주입 → `await` 대상(`wait`, `fetch`)이 signal-aware해야 진짜 취소됨
- **재진입**: 새 실행 시 직전 iterator abort
- **error path**: generator 내부 throw → 상태 노출
- **unmount/teardown**: cleanup으로 in-flight iterator abort
- **stale closure**: 최신 클로저 참조 (hook은 ref, actor는 input)
- **typed**: `phase`/`data` 모두 좁힘

## 어디에 맞는가

generator는 **시간이 코드의 한 방향(↓)으로만 흐르는** 모델. 그 모양에 맞는 시나리오:

- 멀티 단계 loading 메시지 (위 `loadingFlow`)
- progressive search — tier별로 점진적 결과 yield, tier 경계가 자연스러운 cancellation point, debounce 불필요
- optimistic mutation — `{ phase: 'optimistic' | 'reconciled' | 'rolledBack', data }` 전이를 4개 콜백 대신 한 함수로
