타입스크립트 교재 · 15편 / 20편

비동기 + Promise<T> — async 반환 타입

async/await 가 타입과 어떻게 어울리는지. fetch 결과 안전하게 받기.

중급읽는 시간 7분2026-05-17
Promise<T> 가 풀려서 T 가 되는 흐름과 await 의 역할 도식

JS 의 비동기(15편) 위에 TS 가 더하는 한 가지 — "이 비동기가 반환하는 값의 타입". Promise<User> 같은 표기로 컴파일 시점에 결과 모양이 보장됩니다. 15편은 async 반환 타입, fetch 결과 타이핑, catch 의 unknown 까지.

Promise<T> — 미래의 값 타입

// Promise 직접 만들기 — 거의 안 씀 (라이브러리 내부용)
const p: Promise<number> = new Promise((resolve, reject) => {
  setTimeout(() => resolve(42), 100);
});

p.then(v => v + 1);   // v: number

// 실전 — 대부분 async 함수가 자동으로 Promise 반환
async function getNumber(): Promise<number> {
  return 42;
}
//        ^ 반환 타입은 Promise<number>
//          본문에서 return 42 (number) 하지만 자동 wrap

async 함수 — 반환 타입의 규칙

// 명시
async function loadUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

// 추론에 맡김 (권장 — 라이브러리 export 만 명시)
async function loadUser(id: number) {
  const res = await fetch(`/api/users/${id}`);
  return res.json() as Promise<User>;
}
//        ^? Promise<User>

// 에러 케이스 — null 가능성
async function findUser(id: number): Promise<User | null> {
  const u = await db.findOne({ id });
  return u ?? null;
}

await — Promise 를 풀어주기

// await 의 타입은 Awaited<T>
const u: User = await loadUser(1);
//        ^ Promise<User> 의 await → User

// 중첩 Promise 도 자동으로 한 번에
async function nested(): Promise<Promise<number>> {
  return Promise.resolve(Promise.resolve(42));
}
const n = await nested();   // n: number (이중 Promise 자동 평탄화)

await 의 한 줄 의미. "이 Promise 가 풀릴 때까지 기다린 다음 값을 꺼낸다". 반환 타입에 Promise 가 보이면 await 해서 풀어야 함. 안 풀고 .toFixed() 같은 거 호출하면 컴파일 에러.

fetch 결과 타이핑 — 외부 입력 안전하게

// fetch 의 res.json() 은 Promise<any> — 위험!
async function loadUserUnsafe(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  return res.json();   // any 가 자동으로 User 로 — 검증 없이
}

// 안전 패턴 1 — 사용자 정의 타입 가드
async function loadUserGuard(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  const raw: unknown = await res.json();
  if (!isUser(raw)) throw new Error("형식 이상");
  return raw;
}

// 안전 패턴 2 — zod (실전 표준)
import { z } from "zod";
const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
});
type User = z.infer<typeof UserSchema>;

async function loadUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  const raw = await res.json();
  return UserSchema.parse(raw);   // throw if invalid
}

Promise.all · allSettled · race · any

// 모두 성공해야 — 하나 실패하면 즉시 reject
const [users, posts] = await Promise.all([
  fetch("/users").then(r => r.json()),
  fetch("/posts").then(r => r.json()),
]);
// 타입: [any, any] — 위 안전 패턴 적용해 좁히기

// 모두 끝날 때까지 (실패도 받음)
const results = await Promise.allSettled([p1, p2, p3]);
for (const r of results) {
  if (r.status === "fulfilled") console.log(r.value);
  else console.error(r.reason);
}

// 가장 먼저 settle 된 거 (성공/실패 무관)
const winner = await Promise.race([p1, p2]);

// 가장 먼저 성공 (실패는 무시)
const first = await Promise.any([p1, p2]);
메서드언제실패 처리
all모두 결과 필요하나 실패 → 전체 실패
allSettled결과·실패 모두 확인개별 reason
race가장 빠른 응답·타임아웃먼저 실패해도 결과
any하나만 성공하면 OK (fallback)모두 실패 → AggregateError

catch 에서 unknown

try {
  await loadUser(1);
} catch (err) {
  // 기본 — err: unknown (strict 의 useUnknownInCatchVariables)
  if (err instanceof Error) {
    console.error(err.message);
  } else {
    console.error("이상한 것:", err);
  }
}

// 헬퍼로 깔끔하게
function isError(x: unknown): x is Error {
  return x instanceof Error;
}
function getMsg(x: unknown): string {
  return isError(x) ? x.message : String(x);
}

비동기 + 제네릭 — API 클라이언트 패턴

async function api<T>(url: string, schema: z.ZodSchema<T>): Promise<T> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const raw = await res.json();
  return schema.parse(raw);
}

// 사용 — 한 함수로 모든 엔드포인트
const user = await api("/api/users/1", UserSchema);    // user: User
const posts = await api("/api/posts",   z.array(PostSchema));  // posts: Post[]

타임아웃 패턴 — race 활용

function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
  return Promise.race([
    promise,
    new Promise<T>((_, reject) =>
      setTimeout(() => reject(new Error("Timeout")), ms)
    ),
  ]);
}

const user = await withTimeout(loadUser(1), 5000);

// ES2024 표준 — AbortSignal.timeout
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });

async 에서 던진 에러는 자동으로 reject. async 함수 안에서 throw new Error() 하면 호출자의 await 에서 throw 됩니다. 일반 함수처럼 try/catch 로 받으면 끝.

16편 — 데코레이터 (고급 시작)

class·method·property 데코레이터, 메타 정보의 메타 도구.

📚 쉽게 배우는 타입스크립트 교재
이전: 14편 타입 가드 · 현재: 15편 (중급 마지막) · 다음 → 16편 데코레이터 (고급 시작) · 진행: 15/20

© 2026 주나이테크(주) @JUNAITECH