JavaScript · 브라우저
fetch 요청 취소와 에러 처리
AbortController로 요청을 취소한 경우와 HTTP 500 응답을 비교했다. 취소한 뒤에도 서버 작업이 계속되는 경우를 로컬 예제로 확인했다.
목차
페이지를 떠난 뒤 남은 요청 에러를 보면서 궁금했던 게 있었다. 브라우저에서 요청을 취소하면 서버 작업도 멈추는 걸까. HTTP 500이나 Failed to fetch도 같은 실패로 봐도 되는지 정리했다.
아래는 브라우저의 fetch와 로컬 Node.js 서버를 사용한 예제다. 실제 서비스나 DB는 연결하지 않았다.
HTTP 500 응답
fetch는 HTTP 500 응답을 받았다는 이유만으로 catch에 들어가지 않는다. 서버가 보낸 응답을 받은 뒤 response.ok나 response.status를 확인해야 한다. MDN fetch 문서
try {
const response = await fetch('/http-error')
console.log(response.status, response.ok) // 500 false
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
} catch (error) {
console.log(error.message) // HTTP 500
}로컬 서버에서 500을 반환하도록 해보니 fetch는 Response를 반환했다. 이 예제의 catch는 그 아래에서 직접 던진 오류를 처리한다.
AbortController로 취소하기
이번에는 응답을 늦게 보내는 /slow에 요청하고 250ms 뒤 취소했다. 브라우저에서는 AbortError가 발생했다.
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), 250)
try {
const response = await fetch('/slow', {
method: 'POST',
signal: controller.signal,
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
await response.json()
} catch (error) {
if (error instanceof DOMException && error.name === 'AbortError') {
console.log('요청 취소')
} else {
console.error(error)
}
} finally {
clearTimeout(timer)
}위 코드는 기본 abort()를 사용한 예제다. abort(reason)으로 사유를 직접 넘기거나 다른 방식으로 타임아웃을 설정하면 거절 사유가 달라질 수 있다. MDN abort 문서
await fetch()가 끝났어도 본문을 읽는 중이라면 취소될 수 있다. response.json()에서 나는 오류도 처리 범위에 포함해야 한다. MDN 요청 취소 예제
서버 작업은 어떻게 됐을까
재현 서버는 요청을 받으면 1.5초 뒤 메모리의 완료 횟수를 올리도록 만들었다. 연결이 끊겼을 때 타이머를 중단하는 코드는 넣지 않았다.
// Node.js 요청 핸들러 안의 일부
started += 1
setTimeout(() => {
completed += 1
console.log('server: completed')
if (!res.destroyed) {
res.end(JSON.stringify({ completed }))
}
}, 1500)브라우저에서 취소한 뒤 1.6초를 기다렸다가 별도 요청으로 서버 상태를 조회했다.
브라우저 예외: AbortError
서버 요청 시작: +1
서버 작업 완료: +1이 서버에서는 연결이 끊겨도 타이머가 실행됐다. 모든 서버가 반드시 끝까지 실행한다는 뜻은 아니다. 서버가 연결 종료를 어떻게 처리하는지에 따라 달라진다.
그래도 브라우저에서 취소됐다는 사실만으로 저장이 안 됐다고 판단할 수는 없다. 이미 처리한 서버 변경이 자동으로 되돌아가는 것도 아니다. React 문서에서도 요청 취소가 DB 변경을 되돌리지는 않는다고 설명한다. React의 취소 관련 주의점
화면 이동과 Server Action
React에서 컴포넌트가 사라진다고 직접 시작한 fetch가 전부 자동으로 취소되는 것은 아니다. Effect에서 요청했다면 cleanup에서 취소하거나 더 이상 필요 없는 결과를 반영하지 않도록 처리해야 한다. 결과를 무시하는 코드는 요청 자체를 중단하는 코드와는 다르다. React Effect의 데이터 조회 예제
Server Action 호출에도 위의 { signal } 옵션을 그대로 붙일 수는 없다. 현재 공개된 호출 방식은 fetch의 전송 옵션과 다르고 함수 인자도 React가 지원하는 형태로 직렬화되어야 한다. AbortSignal은 지원되는 인자 타입이 아니다. React 인자 직렬화
여기서는 일반 fetch의 취소만 재현했다. Server Action의 화면 이동 중 동작을 같은 실험으로 확인한 것은 아니다. 호출 방식 자체의 차이는 Server Action과 일반 API 요청의 차이에 따로 적었다.
에러 로그에서 구분할 것
TypeError: Failed to fetch나 Load failed라는 메시지만으로 취소였다고 확정할 수는 없다. 네트워크 문제 등 여러 이유로 TypeError가 발생할 수 있다. MDN fetch 예외
직접 취소한 요청인지, HTTP 응답을 받았는지, 화면 이동 시점과 겹쳤는지부터 확인해야 한다. 의도한 화면 이탈 취소와 사용자가 기다리다 실패한 요청을 같은 기준으로 필터링하고 싶지는 않다. 저장 요청이라면 응답을 못 받았다는 이유로 바로 다시 보내기 전에 서버에 반영됐는지도 확인할 필요가 있다.