빠른 흐름
// 타임아웃과 HTTP 오류 처리를 포함한 실전 패턴
async function fetchJson(url, timeoutMs = 5000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} finally {
clearTimeout(timer);
}
}
const user = await fetchJson("https://api.example.com/users/1");
console.log(user.name);요청 흐름
먼저 머릿속에 들어와야 하는 기본형은 아래입니다.
- 상태 코드 확인:
if (!response.ok) throw ... - JSON 파싱:
await response.json() - 타임아웃:
AbortSignal.timeout(ms)또는AbortController - 본문 한 번 읽기:
json()/text()중 하나 - 실패 응답 본문을 따로 읽고 싶으면 분기 설계
catch는 네트워크 실패만 잡는다
fetch가 reject되는 건 DNS 오류, 연결 거부, abort 같은 네트워크 계열입니다. 404나 500은 정상 응답이므로 response.ok를 따로 봐야 합니다.
try {
const response = await fetch(url);
const data = await response.json();
} catch (err) {
// 네트워크 실패만 여기로 온다
}즉 catch만 붙여 두고 상태 코드를 안 보면 서버 오류 응답이 성공처럼 흘러갈 수 있습니다.
JSON을 읽기 전에 상태를 먼저 본다
실패 응답도 JSON일 수는 있지만, 보통은 상태 코드 확인이 먼저입니다. response.ok를 보고 실패면 에러로 바꾸고, 그다음 성공 응답을 response.json()으로 읽는 편이 흐름이 분명합니다.
실패 응답 본문까지 로그나 에러 객체에 넣고 싶다면, 성공/실패 경로를 나눠 text()나 json()을 한 번만 읽도록 설계하는 편이 안전합니다.
애플리케이션 타임아웃은 직접 설계하는 편이 안전하다
Node의 내장 fetch는 앱이 기대하는 짧은 요청 제한 시간을 자동으로 맞춰 주지 않습니다. API 호출 카드라면 AbortSignal.timeout() 또는 AbortController를 같이 떠올리는 편이 좋습니다.
const response = await fetch(url, {
signal: AbortSignal.timeout(5000),
});응답 본문은 한 번만 읽는다
response.json()을 읽은 뒤 다시 response.text()를 읽을 수는 없습니다. 디버깅용 로그와 실제 파싱을 둘 다 하고 싶다면 설계를 다시 나눠야 합니다.
JSON 파싱 실패와 HTTP 실패는 다른 문제다
상태 코드는 200이어도 본문이 JSON이 아니면 response.json()에서 다시 실패할 수 있습니다.
즉 네트워크 실패, HTTP 실패, 본문 파싱 실패를 서로 다른 문제로 나눠 읽어야 합니다.
재시도는 모든 실패에 똑같이 걸지 않는다
timeout이나 일시적 503은 재시도 후보가 될 수 있지만, 400 계열이나 JSON 파싱 실패는 보통 입력/계약 문제에 가깝습니다. 그래서 fetch JSON 카드는 호출 성공 여부뿐 아니라 "어떤 실패를 재시도할 것인가"를 나누는 카드이기도 합니다.
어떤 실패를 나눌까
체크포인트
- HTTP 실패 확인:
response.ok - JSON 응답 읽기:
response.json() - 타임아웃:
AbortSignal.timeout(ms)또는AbortController - 200이어도 JSON 파싱 실패 가능성 확인
- 텍스트 응답:
response.text() - 바이너리 응답:
response.arrayBuffer() - 재시도할 실패와 바로 실패 처리할 실패를 나눈다
주의할 점
catch만 붙인 fetch는 서버 오류를 놓친다. 404나 500도 Promise는 resolve되므로, response.ok 검사를 빼면 실패 응답이 성공처럼 흘러간다.
참고 링크
1 sources