Menu

JavaScript Fetch API 완벽 가이드: 요청, JSON, 에러 처리

자바스크립트 Fetch API 사용법을 정리했습니다. GET/POST 요청 보내기, JSON 파싱, 제대로 된 에러 처리, 그리고 느린 요청을 취소하는 방법까지 다룹니다.

Promise 기반 HTTP 클라이언트, Fetch

fetch는 브라우저와 최신 Node에 기본으로 내장되어 있습니다. URL을 넘기면 Response 객체로 이행(resolve)되는 Promise를 돌려주죠. 핵심만 놓고 보면 이게 Fetch API의 전부입니다:

index.js
Output
Click Run to see the output here.

.then을 두 번 쓰는 이유는 비동기 단계가 두 번 있기 때문입니다. 먼저 응답 헤더가 도착하면 첫 번째 프로미스가 그걸로 resolve되고, 그다음 본문을 읽어서 파싱하는 단계가 이어집니다. 이때 response.json() 자체도 프로미스를 반환합니다. 본문은 여러분이 직접 요청하기 전까지는 다운로드되지 않는다는 점이 포인트예요.

같은 흐름을 async/await로 쓰면 위에서 아래로 읽히는 평범한 코드처럼 보입니다:

index.js
Output
Click Run to see the output here.

await 두 번, 일시 중단 지점도 두 번. 하는 일은 똑같지만 읽는 순서가 훨씬 명확해졌습니다.

Response 객체 살펴보기

fetch가 돌려주는 건 응답 본문이 아닙니다. 메타데이터와 함께, 본문을 다양한 형태로 읽을 수 있는 메서드를 가진 Response 객체죠.

index.js
Output
Click Run to see the output here.

응답 본문은 .json(), .text(), .blob(), .arrayBuffer(), .formData() 중 하나로 읽을 수 있고, 각각은 프로미스를 반환합니다. 단, 본문은 한 번만 읽을 수 있어요. 같은 응답에 대해 .json()을 두 번 호출하면 두 번째 호출에서 예외가 발생합니다.

가장 큰 함정: HTTP 에러는 reject되지 않는다

fetch api를 처음 쓰는 사람이라면 거의 다 여기서 한 번씩 걸립니다. 404나 500 응답은 프로미스가 reject되는 게 아닙니다. 프로미스는 정상적으로 resolve되고, 대신 response.ok === false가 될 뿐이죠. fetch가 reject되는 건 요청 자체가 완료되지 못했을 때 — 즉 DNS 실패, 네트워크 끊김, CORS 차단 같은 경우뿐입니다.

그래서 아무 생각 없이 짠 fetch 코드는 에러 페이지를 그대로 받아 넘긴 뒤, 뒤늦게 .json()에서 터져버리기 일쑤입니다:

index.js
Output
Click Run to see the output here.

서버가 에러 상태 코드를 반환했을 때는 response.ok를 직접 확인해서 에러를 던져야 합니다:

index.js
Output
Click Run to see the output here.

if (!response.ok) 블록을 쓰는 습관을 들이세요. 앞으로 만들 모든 fetch 래퍼 함수에 꼭 들어가야 하는 코드입니다.

fetch POST 요청 보내기

기본 메서드는 GET이고, 그 외의 요청을 보내려면 두 번째 인자로 옵션 객체를 넘기면 됩니다.

index.js
Output
Click Run to see the output here.

짚고 넘어갈 포인트 세 가지입니다.

  • method의 기본값은 "GET"입니다. POST, PUT, DELETE, PATCH를 쓰려면 직접 명시해야 합니다.
  • body에는 문자열(또는 FormData, Blob 등)을 넣어야 합니다. fetch는 객체를 알아서 직렬화해주지 않으니 JSON.stringify(...)는 직접 호출해야 합니다.
  • Content-Type 헤더는 서버에 본문을 어떻게 파싱할지 알려줍니다. 빼먹으면 대부분의 서버는 본문을 그냥 일반 텍스트로 취급해버립니다.

헤더, 쿼리 스트링, 그 밖의 옵션들

헤더는 평범한 객체(또는 Headers 인스턴스)로 넘기면 되고, 쿼리 스트링은 보통 URLSearchParams로 직접 조립합니다.

index.js
Output
Click Run to see the output here.

URLSearchParams를 쓰면 인코딩은 알아서 처리됩니다. 공백이든 & 기호든 유니코드든, 이스케이프가 필요한 문자가 들어와도 URL이 깨질 일이 없죠.

실전 코드에서 자주 보게 되는 다른 옵션들도 있습니다. 크로스 오리진 요청에 쿠키를 함께 보내려면 credentials: "include", HTTP 캐시를 건너뛰려면 cache: "no-store", CORS 동작을 제어하려면 mode: "cors"(대부분 기본값)를 씁니다.

AbortController로 fetch 요청 취소하기

가끔은 요청을 중간에 포기해야 할 때가 있습니다. 사용자가 검색어를 새로 입력했거나, 응답이 너무 오래 걸리는 경우처럼요. 이럴 때 쓰는 게 바로 AbortController입니다.

index.js
Output
Click Run to see the output here.

controller.abort()를 호출하면 fetch 프라미스는 name"AbortError"DOMException과 함께 reject 됩니다. finally 블록에서 타이머를 정리하기 때문에, 요청이 성공했을 때 타임아웃이 그대로 남아 도는 일도 없습니다.

이 패턴 — fetch + 타임아웃 + 정리(cleanup) — 은 헬퍼 함수로 감싸서 여기저기서 재사용할 만한 가치가 있습니다.

재사용 가능한 fetch 래퍼 만들기

지금까지 다룬 내용을 한데 모으면, 반복되는 보일러플레이트를 한 번에 처리해 주는 작은 헬퍼가 완성됩니다:

index.js
Output
Click Run to see the output here.

헤더 바꿀 곳 하나, 에러 처리할 곳 하나, 빈 응답 다룰 곳 하나. 규모가 조금이라도 있는 앱이라면 결국 이런 형태로 수렴하게 됩니다.

다음: 비동기 코드에서의 에러 처리

fetch는 비동기 에러가 가장 자주 드러나는 지점 중 하나이고, response.ok 체크는 그중 한 조각일 뿐입니다. 다음 페이지에서는 프로미스와 async/await 전반에 걸친 에러 처리를 다룹니다. 에러가 어디로 흘러가는지, 어떻게 잡아내는지, 그리고 조용히 빠져나가게 만드는 함정들까지 살펴볼게요.

자주 묻는 질문

자바스크립트에서 fetch는 어떻게 사용하나요?

원하는 URL을 넣고 fetch(url)을 호출하면 됩니다. 이 함수는 Response 객체로 resolve되는 Promise를 반환합니다. 응답 본문을 파싱하려면 response.json()을 호출하는데, 이것도 프로미스를 반환해요. async/await로 쓰면 이렇게 됩니다: const res = await fetch(url); const data = await res.json();.

fetch로 POST 요청은 어떻게 보내나요?

두 번째 인자로 옵션 객체를 넘기면 됩니다. method: 'POST'를 지정하고, headers에 보통 'Content-Type': 'application/json'을 넣고, body에는 JSON.stringify(...)로 직렬화한 문자열을 넣어야 합니다. fetch는 객체를 알아서 직렬화해 주지 않으니 꼭 직접 처리해야 해요.

fetch는 왜 404나 500에서 reject되지 않나요?

fetch는 네트워크 자체가 실패했을 때만 reject합니다. DNS 오류, 연결 불가, CORS 차단 같은 경우죠. HTTP 에러 상태 코드(404, 500 등)는 프로미스 입장에서는 '정상적으로 응답이 돌아온 것'으로 간주합니다. 그래서 response.ok(200~299일 때 true)나 response.status를 직접 확인하고, 서버에서 에러가 왔다면 직접 throw해 줘야 합니다.

fetch 요청을 중간에 취소할 수 있나요?

네, AbortController로 가능합니다. 컨트롤러를 하나 만들고, 옵션 객체의 signal 속성에 controller.signal을 넘긴 뒤, 취소하고 싶을 때 controller.abort()를 호출하면 됩니다. 그러면 fetch 프로미스가 AbortError로 reject되기 때문에 catch에서 처리하면 돼요.

Coddy로 코딩 배우기

시작하기