--- id: async-programming-promise-async-await title: "비동기 프로그래밍 Promise async await" category: "Programming_Language" status: "draft" verification_status: "applied" canonical_id: "" aliases: ["Promise", "async", "await", "비동기", "AbortSignal", "동시성", "스트리밍", "concurrency"] duplicate_of: "" source_trust_level: "A" confidence_score: 0.93 created_at: 2026-06-13 updated_at: 2026-06-13 review_reason: "" merge_history: [] tags: ["typescript", "javascript", "async", "promise", "abortsignal", "astraai"] raw_sources: ["AstraAI/src/core/services.ts", "AstraAI/src/core/lock.ts", "AstraAI/src/core/queue.ts", "AstraAI/src/features/providers/index.ts"] applied_in: ["AstraAI"] github_commit: "" --- # [[비동기 프로그래밍 Promise async await]] ## 🎯 한 줄 통찰 (One-line insight) `async/await` 는 비동기 코드를 동기처럼 읽히게 하는 문법이고, `Promise` 는 그 토대이며, AstraAI 는 여기에 **`AbortSignal` 결합·타임아웃 경쟁(race)·동시성 제한**을 더해 "취소 가능하고 폭주하지 않는" 비동기를 구현한다 [S1][S2]. ## 🧠 핵심 개념 (Core concepts) 1. **Promise:** 미래의 값을 담는 객체. `pending → fulfilled/rejected` 상태를 가진다. `new Promise((resolve, reject) => ...)` 로 직접 만들거나 `async` 함수가 자동 반환한다 [S2]. 2. **async/await:** `async` 함수 안에서 `await promise` 는 Promise 가 풀릴 때까지 기다린 *값* 을 돌려준다. 실패하면 예외로 던져져 `try/catch` 로 잡는다 [S1]. 3. **`Promise.all` / `Promise.race`:** `all` 은 모두 완료될 때까지 병렬 대기(하나라도 실패 시 전체 reject), `race` 는 가장 먼저 끝난 하나를 채택 — AstraAI 는 race 로 "작업 vs 타임아웃" 경쟁을 만든다 [S3]. 4. **`AbortSignal` / `AbortController`:** 진행 중인 비동기(특히 `fetch`)를 외부에서 취소하는 표준 메커니즘. `AbortSignal.timeout(ms)`, `AbortSignal.any([...])` 로 타임아웃·사용자 취소를 결합 [S1]. 5. **동시성 제한 (Concurrency limiting):** 무한 병렬은 자원을 고갈시킨다. 큐로 동시 실행 수를 `max(2, cpus-1)` 로 제한 [S4]. ## 🧩 추출된 패턴 (Extracted patterns) - **타임아웃 + 외부 취소 신호 결합:** `AbortSignal.any([req.signal, AbortSignal.timeout(timeoutMs)])` — 둘 중 무엇이 먼저 fire 돼도 fetch 가 즉시 중단된다. 사용자가 "Stop" 을 누르면 LLM 생성 도중에도 끊긴다 [S1]. - **race 로 데드락 방지:** lock 획득 시 `Promise.race([previousPromise, timeoutPromise])` — 앞 작업이 영원히 안 끝나도 timeout 이 깨운다 [S2]. - **resolve 를 밖으로 빼내는 deferred:** `let release; new Promise(r => { release = r; })` — Promise 를 만들고 그 resolve 함수를 외부에서 호출 가능하게 보관(락 해제 함수로 반환) [S2]. - **큐 기반 동시성 캡:** `enqueue` 가 Promise 를 반환하되 실제 실행은 `activeCount < limit` 일 때만 — 초과분은 대기 [S4]. - **best-effort 비차단:** `void ensureEmbeddingConfigured(context)` — 결과를 기다리지 않고 백그라운드로 흘려보내는 fire-and-forget (`void` 로 의도 명시) [S1]. ## 📖 세부 내용 (Details) ### await 의 실패는 예외다 `await fetch(...)` 가 네트워크 오류로 reject 되면 그 지점에서 throw 된다. AstraAI 의 `AIService.chat` 은 엔진별 루프 안에서 `try/catch` 로 잡아 `lastError` 에 저장하고 다음 엔진으로 폴백한다 — "한 엔진 실패가 전체 실패가 아니다" [S1]. ### AbortSignal 결합 (핵심 패턴) ```typescript const timeoutSignal = AbortSignal.timeout(timeoutMs); const combinedSignal = req.signal ? AbortSignal.any([req.signal, timeoutSignal]) // 사용자 취소 OR 타임아웃 : timeoutSignal; const res = await fetch(apiUrl, { /* ... */ signal: combinedSignal }); ``` 이 패턴 덕분에 (1) 응답이 너무 느리면 타임아웃으로, (2) 사용자가 멈추면 외부 signal 로 즉시 중단된다. 긴 multi-turn 경로(dispatcher 등)에는 반드시 `signal` 을 전달하는 것이 규칙 [S1]. ### 직접 만드는 Promise (deferred 패턴) 락 매니저는 "다른 코드가 부를 때 풀리는 Promise" 가 필요하다: ```typescript let release!: () => void; const newPromise = new Promise((resolve) => { release = resolve; }); // ... 작업이 끝나면 호출부가 release() 를 부르면 newPromise 가 fulfilled return () => { release(); /* cleanup */ }; ``` ### 병렬 vs 순차 - 독립 작업은 `Promise.all([a(), b()])` 로 병렬 (provider 모델 목록 동시 조회) [S5]. - 의존 작업은 순차 `await a(); await b();`. - 자원 부담이 큰 대량 작업은 `Promise.all` 대신 동시성 제한 큐를 쓴다 [S4]. ## ⚖️ 모순 및 업데이트 (Contradictions & updates) - **`Promise.all` 의 함정:** 하나라도 reject 되면 전체가 reject 되고 나머지 성공 결과를 잃는다. 부분 실패를 허용해야 하면 `Promise.allSettled` 를 쓰거나 각 작업을 try/catch 로 감싸야 한다. - **`await` in loop vs 병렬:** 루프 안 `await` 는 순차 실행이라 느릴 수 있다. 단, AstraAI 의 엔진 폴백 루프는 *의도적으로 순차* (앞 엔진이 성공하면 뒤는 안 부름). - **`forEach` + async 주의:** `array.forEach(async ...)` 는 완료를 기다리지 않는다. 대기하려면 `for...of` + `await` 또는 `Promise.all(array.map(...))`. ## 🛠️ 적용 사례 (Applied in summary) - `AstraAI/src/core/services.ts` — AbortSignal 결합 + 엔진 폴백 루프(try/catch 순차) [S1]. - `AstraAI/src/core/lock.ts` — deferred Promise + `Promise.race` 타임아웃 [S2]. - `AstraAI/src/core/queue.ts` — 동시성 제한 큐 [S4]. - `AstraAI/src/features/providers/index.ts` — `Promise.all(tasks)` 로 provider 목록 병렬 조회 [S5]. ## 💻 코드 패턴 (Code patterns) ```typescript // 1) 타임아웃 + 외부 취소 결합 (src/core/services.ts) const timeoutSignal = AbortSignal.timeout(timeoutMs); const combinedSignal = req.signal ? AbortSignal.any([req.signal, timeoutSignal]) : timeoutSignal; const res = await fetch(apiUrl, { method: 'POST', body: JSON.stringify(payload), signal: combinedSignal }); // 2) try/catch 폴백 루프 — 한 엔진 실패가 전체 실패가 아님 (src/core/services.ts) let lastError: Error | null = null; for (const engine of engines) { try { const r = await callEngine(engine); if (r) return r; } catch (e: any) { lastError = e instanceof Error ? e : new Error(String(e)); } } throw lastError ?? new Error('All engines failed.'); // 3) deferred Promise + race 타임아웃 (src/core/lock.ts) let release!: () => void; const newPromise = new Promise((resolve) => { release = resolve; }); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Lock timed out')), timeoutMs)); await Promise.race([previousPromise, timeoutPromise]); // 4) fire-and-forget (의도적 비대기) — void 로 명시 (src/extension.ts) void ensureEmbeddingConfigured(context); // 5) 병렬 수집 (src/features/providers/index.ts) const tasks: Array> = []; tasks.push(listOpenRouterModels(ctx).then((ids) => ids.forEach(pushModel))); await Promise.all(tasks); ``` ## ✅ 검증 상태 및 신뢰도 - **상태:** draft - **검증 단계:** applied - **출처 신뢰도:** A - **신뢰 점수:** 0.93 - **중복 검사 결과:** 신규 생성 (New discovery) ## 🔗 지식 그래프 (Knowledge Graph) - **상위/루트:** [[TypeScript 기초와 타입 시스템]] - **관련 개념:** [[동시성 제어 Lock Queue Transaction]], [[LLM 프로바이더 추상화]], [[에러 처리와 커스텀 에러]] - **참조 맥락:** 로컬 LLM 이 fetch/취소/타임아웃/병렬 처리를 작성할 때 참조. ## 📚 출처 (Sources) - [S1] AstraAI/src/core/services.ts — AbortSignal.any/timeout, 엔진 폴백, void fire-and-forget(extension.ts 포함) - [S2] AstraAI/src/core/lock.ts — deferred Promise, Promise.race 타임아웃 - [S3] (general) Promise.all/race 의미론 - [S4] AstraAI/src/core/queue.ts — 동시성 제한 큐 - [S5] AstraAI/src/features/providers/index.ts — Promise.all 병렬 수집 ## 📝 변경 이력 (Change history) - 2026-06-13: AstraAI 코드 분석 기반 초안 생성.