[Javascript] Axios 파일 업로드 및 다운로드

파일 업로드 및 다운로드

📁 파일 업로드

// 단일 파일 업로드
async function uploadFile(file, onProgress) {
const formData = new FormData();
formData.append('file', file);
formData.append('category', 'documents');
formData.append('description', [file.name](http://file.name));

try {
const response = await [axios.post](http://axios.post)('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / [progressEvent.total](http://progressEvent.total)
);
console.log(`업로드 진행률: ${percentCompleted}%`);
onProgress?.(percentCompleted);
}
});

return [response.data](http://response.data);
} catch (error) {
console.error('파일 업로드 실패:', error);
throw error;
}
}

// 다중 파일 업로드
async function uploadMultipleFiles(files) {
const formData = new FormData();

files.forEach((file, index) => {
formData.append(`files`, file);
});

const response = await [axios.post](http://axios.post)('/api/upload/multiple', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});

return [response.data](http://response.data);
}

// 사용 예시
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (event) => {
const file = [event.target](http://event.target).files[0];
if (file) {
try {
const result = await uploadFile(file, (progress) => {
console.log(`업로드 진행률: ${progress}%`);
});
console.log('업로드 완료:', result);
} catch (error) {
console.error('업로드 에러:', error);
}
}
});

📥 파일 다운로드

// 파일 다운로드
async function downloadFile(fileId, filename) {
try {
const response = await axios.get(`/api/files/${fileId}/download`, {
responseType: 'blob'
});

// Blob을 사용하여 파일 다운로드 트리거
const url = window.URL.createObjectURL(new Blob([[response.data](http://response.data)]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
[link.click](http://link.click)();
document.body.removeChild(link);

// 메모리 정리
window.URL.revokeObjectURL(url);

} catch (error) {
console.error('파일 다운로드 실패:', error);
}
}

에러 처리 베스트 프랙티스

🛡️ 포괄적인 에러 처리

// 에러 타입별 처리 함수
function handleApiError(error) {
if (error.response) {
// 서버가 응답했지만 상태 코드가 2xx 범위를 벗어남
const { status, data } = error.response;

switch (status) {
case 400:
console.error('잘못된 요청:', data.message);
break;
case 401:
console.error('인증 실패 - 로그인이 필요합니다');
// 로그인 페이지로 리다이렉트
window.location.href = '/login';
break;
case 403:
console.error('접근 권한이 없습니다');
break;
case 404:
console.error('요청한 리소스를 찾을 수 없습니다');
break;
case 429:
console.error('요청 한도를 초과했습니다. 잠시 후 다시 시도해주세요');
break;
case 500:
console.error('서버 내부 오류가 발생했습니다');
break;
default:
console.error(`서버 오류 (${status}):`, data.message);
}

return { type: 'response', status, message: data.message };

} else if (error.request) {
// 요청은 전송되었지만 응답을 받지 못함 (네트워크 오류)
console.error('네트워크 오류: 서버에 연결할 수 없습니다');
return { type: 'network', message: '네트워크 연결을 확인해주세요' };

} else {
// 요청 설정 중에 오류 발생
console.error('요청 설정 오류:', error.message);
return { type: 'config', message: error.message };
}
}

// 재시도 로직이 포함된 API 호출
async function apiCallWithRetry(requestFn, maxRetries = 3, delay = 1000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await requestFn();
} catch (error) {
const errorInfo = handleApiError(error);

if (attempt === maxRetries || errorInfo.type === 'response') {
throw error; // 최대 재시도 횟수 도달 또는 서버 응답 오류
}

console.log(`재시도 ${attempt}/${maxRetries} - ${delay}ms 후 다시 시도...`);
await new Promise(resolve => setTimeout(resolve, delay * attempt));
}
}
}

// 사용 예시
async function fetchUserData(userId) {
return apiCallWithRetry(
() => axios.get(`/api/users/${userId}`),
3, // 최대 3번 재시도
1000 // 1초 간격
);
}

성능 최적화 팁

⚡ 요청 최적화 기법

// 1. 요청 취소 (AbortController 사용)
class ApiService {
constructor() {
this.pendingRequests = new Map();
}

async fetchData(endpoint, options = {}) {
// 이전 요청이 있다면 취소
if (this.pendingRequests.has(endpoint)) {
this.pendingRequests.get(endpoint).abort();
}

const controller = new AbortController();
this.pendingRequests.set(endpoint, controller);

try {
const response = await axios.get(endpoint, {
...options,
signal: controller.signal
});

this.pendingRequests.delete(endpoint);
return [response.data](http://response.data);

} catch (error) {
this.pendingRequests.delete(endpoint);

if (axios.isCancel(error)) {
console.log('요청이 취소되었습니다:', endpoint);
} else {
throw error;
}
}
}

// 모든 대기중인 요청 취소
cancelAllRequests() {
this.pendingRequests.forEach(controller => controller.abort());
this.pendingRequests.clear();
}
}

// 2. 요청 디바운싱 (검색 등에 유용)
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}

// 검색 API 호출 최적화
const debouncedSearch = debounce(async (query) => {
if (query.trim()) {
const results = await axios.get('/api/search', {
params: { q: query }
});
console.log('검색 결과:', [results.data](http://results.data));
}
}, 300);

// 3. 동시 요청 제한
class RateLimitedApi {
constructor(maxConcurrent = 5) {
this.maxConcurrent = maxConcurrent;
this.running = 0;
this.queue = [];
}

async request(config) {
return new Promise((resolve, reject) => {
this.queue.push({ config, resolve, reject });
this.processQueue();
});
}

async processQueue() {
if (this.running >= this.maxConcurrent || this.queue.length === 0) {
return;
}

this.running++;
const { config, resolve, reject } = this.queue.shift();

try {
const response = await axios(config);
resolve(response);
} catch (error) {
reject(error);
} finally {
this.running--;
this.processQueue(); // 다음 요청 처리
}
}
}

const rateLimitedApi = new RateLimitedApi(3); // 최대 3개 동시 요청

🎯 마무리

Axios는 JavaScript 생태계에서 가장 강력하고 유연한 HTTP 클라이언트 라이브러리입니다. 이 가이드에서 다룬 내용들을 실제 프로젝트에 적용하면 더욱 안정적이고 효율적인 API 통신을 구현할 수 있습니다.

핵심 포인트 정리

인스턴스 활용: 공통 설정을 가진 인스턴스로 코드 중복 제거
인터셉터 사용: 인증, 로깅, 에러처리 등 공통 로직 처리
에러 처리: 다양한 에러 상황에 대한 체계적인 대응
성능 최적화: 요청 취소, 디바운싱, 동시 요청 제한
타입 안정성: TypeScript와 함께 사용하여 더욱 안전한 코드 작성

Axios를 마스터하여 더 나은 웹 애플리케이션을 만들어보세요! 🚀


Share