4.4장: 오류 처리
학습 시간: 45분
1. 오류 처리: 우주선 관제 센터의 비상 프로토콜
우주에서는 모든 것이 계획대로 되지 않을 수 있습니다. 태양 플레어가 통신을 방해할 수도 있고, 우주선 온보드 컴퓨터에 오류가 발생할 수도 있으며, 지구에서 보내는 명령에 잘못된 좌표가 포함될 수도 있습니다.
프론트엔드에서의 오류 처리는 우주선 관제 센터의 비상 프로토콜과 같습니다. 다음과 같아야 합니다:
- 🚨 단 하나의 실패한 명령으로 전체 인터페이스가 "폭발"하는 것을 방지합니다.
- 📡 운영자(사용자)에게 무엇이 잘못되었는지 명확하게 알려줍니다.
- 🔧 가능한 다음 단계를 제안합니다.
💡 우주 비유:
우주선에서
500 Internal Server Error
신호가 오면, 관제 센터 디스플레이에 "57번째 줄에 치명적인 JavaScript 오류 발생"이라고 표시되어서는 안 됩니다. 대신 "🚨 우주선에 장애 발생! 엔지니어들에게 이미 통보되었습니다. 나중에 명령을 다시 시도해 보세요."라고 표시되어야 합니다.
2. "우주 이상 현상"의 유형
프론트엔드에서 우리는 API 작업 시 세 가지 주요 유형의 오류에 직면합니다:
- 네트워크 오류: 서버와 연결되지 않았습니다. 안테나가 작동하지 않거나 케이블이 끊어졌습니다.
fetch
는.catch()
블록으로 "떨어집니다". - 클라이언트 오류 (4xx): 지구에서 보낸 명령이 올바르지 않았습니다. 잘못된 ID, 유효성 검사 오류. 서버는 응답하지만
4xx
상태입니다. - 서버 오류 (5xx): 우주선 자체에 장애가 발생했습니다. API 코드에 문제가 있습니다. 서버는 응답하지만
500+
상태입니다.
우리는 이미 try...catch
와 response.ok
확인을 사용하여 이들을 처리하기 시작했습니다. 이제 중앙 집중식으로 처리해 봅시다.
3. 중앙 집중식 처리 함수
모든 함수에서 동일한 try...catch
코드를 반복하는 것은 좋지 않은 습관입니다. fetch
요청을 위한 범용 "래퍼"를 만들어 봅시다.
단계 1: api.js
생성
app.js
옆에 새로운 api.js
파일을 만드세요. 여기에 API 상호작용의 모든 로직을 추출할 것입니다.
// api.js
const API_BASE_URL = 'http://127.0.0.1:8000';
/**
* API 요청을 실행하기 위한 범용 함수.
* 오류를 처리하고 JSON을 반환합니다.
* @param {string} endpoint - API 엔드포인트, 예: '/spaceships'
* @param {object} options - fetch를 위한 옵션 (method, headers, body)
*/
async function apiRequest(endpoint, options = {}) {
const url = `${API_BASE_URL}${endpoint}`;
try {
const response = await fetch(url, options);
// 응답이 JSON이 아니라면 즉시 오류를 던집니다.
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
// 본문이 없는 성공적인 DELETE 요청에 대한 예외
if (response.status === 204) return null;
throw new TypeError(`서버에서 비-JSON 응답을 받았습니다: ${response.statusText}`);
}
const data = await response.json();
if (!response.ok) {
// 서버가 오류가 포함된 JSON을 반환한 경우 (예: FastAPI의 detail)
const errorMessage = data.detail || `HTTP 오류! 상태: ${response.status}`;
throw new Error(errorMessage);
}
return data;
} catch (error) {
console.error(`${endpoint}에 대한 API 요청 오류:`, error);
// UI에서 잡을 수 있도록 오류를 계속 전파합니다.
throw error;
}
}
단계 2: index.html
에 api.js
연결
app.js
가 이 함수들을 사용할 것이므로 app.js
이전에 연결하는 것이 중요합니다.
<!-- index.html -->
<body>
<!-- ... -->
<script src="api.js"></script>
<script src="app.js"></script>
</body>
단계 3: app.js
리팩토링
이제 새로운 apiRequest
를 사용하여 함수들을 다시 작성해 봅시다.
// app.js
// const API_BASE_URL = ...; // 이 줄은 이제 api.js에 있으므로 삭제할 수 있습니다.
// ...
async function fetchAndDisplayFleet() {
try {
fleetList.innerHTML = '<li>텔레메트리 로드 중...</li>';
const ships = await apiRequest('/spaceships'); // <-- 우리의 래퍼를 사용합니다!
fleetList.innerHTML = '';
if (ships.length === 0) {
fleetList.innerHTML = '<li>레지스트리에 등록된 기기가 없습니다.</li>';
return;
}
ships.forEach(ship => { /* ... 나머지 표시 코드 ... */ });
} catch (error) {
fleetList.innerHTML = `<li>🔴 함대 로드 오류: ${error.message}</li>`;
}
}
async function createShip(event) {
event.preventDefault();
const shipData = { /* ... 폼에서 데이터 수집 ... */ };
try {
createStatusMessage.textContent = '발사 명령 전송 중...';
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(shipData)
};
const newShip = await apiRequest('/spaceships', options); // <-- 우리의 래퍼를 사용합니다!
createStatusMessage.textContent = `🚀 성공적으로 발사되었습니다! 기기 ID: ${newShip.id}`;
createShipForm.reset();
fetchAndDisplayFleet();
} catch (error) {
createStatusMessage.textContent = `🔴 오류: ${error.message}`;
}
}
// 나머지 함수들 (fetchShipById, deleteShip)도 유사하게 다시 작성하세요!
response.ok
확인, JSON 파싱의 모든 로직이 한 곳에 있으며, app.js
의 코드가 훨씬 더 깔끔하고 읽기 쉽게 되었습니다.
4. 사용자에게 오류 표시
좋은 인터페이스는 단순히 오류를 콘솔에만 쓰는 것이 아니라 사용자에게 이해하기 쉬운 형태로 보여주어야 합니다.
예시: createShip
개선
우리 코드는 이미 createStatusMessage.textContent = ...
와 같이 처리하고 있습니다. 하지만 알림을 표시하기 위한 범용 함수를 만들어 더 좋게 만들 수 있습니다.
app.js
에 추가:
// app.js
function showNotification(message, isError = false) {
const notificationArea = document.getElementById('create-status-message'); // 또는 다른 요소
notificationArea.textContent = message;
notificationArea.style.color = isError ? 'red' : 'green';
}
// createShip에서 사용:
async function createShip(event) {
// ...
try {
// ...
const newShip = await apiRequest('/spaceships', options);
showNotification(`🚀 성공적으로 발사되었습니다! ID: ${newShip.id}`);
// ...
} catch (error) {
showNotification(`🔴 오류: ${error.message}`, true);
}
}
개념 확립 퀴즈
🚀 장 요약:
신뢰할 수 있는 비상 프로토콜을 생성하여 MCC를 강화했습니다.
- 🛡️ 네트워크, 클라이언트, 서버 오류의 차이를 이해합니다.
- ⚙️ 모든 요청을 처리하는 중앙 집중식
apiRequest
함수를 생성하여 코드 중복을 피했습니다. - 📡 이제 인터페이스가 사용자에게 오류를 올바르게 보고하여 더욱 사용자 친화적이고 신뢰할 수 있게 되었습니다.
비상 방패 활성화! 하지만 .then()
체인과 최신 async/await
중 어떤 것이 더 좋을까요? 다음 장에서는 두 가지 접근 방식을 모두 살펴보고 언제 어떤 것을 사용해야 하는지 이해할 것입니다.
📌 확인:
app.js
의 코드가 성공적으로 리팩토링되어 새apiRequest
함수를 사용하는지 확인하십시오.- FastAPI 서버를 중지하고 "데이터 요청" 버튼을 눌러보십시오. 페이지에 연결 오류가 표시되어야 합니다.
- 유효하지 않은 데이터로 선박을 생성해 보십시오. FastAPI에서 온 유효성 검사 오류 메시지가 표시되어야 합니다.
⚠️ 오류 발생 시:
apiRequest is not defined
:index.html
에api.js
를app.js
앞에 연결했는지 확인하십시오.- JavaScript의 다른 구문 오류에 대해 브라우저 콘솔을 확인하십시오.