웹에서 파일 업로드할 때 클라이언트에 **업로드 진행률(1~100%)**을 표시하려면,
브라우저가 전송하는 업로드 이벤트를 잡아서 진행률을 계산해서 보여주면 됩니다.
보통 두 가지 방식이 많이 쓰여요:
1. XHR(XMLHttpRequest) 방식
<input type="file" id="fileInput" />
<progress id="progressBar" value="0" max="100"></progress>
<span id="percent">0%</span>
<script>
document.getElementById("fileInput").addEventListener("change", function () {
const file = this.files[0];
if (!file) return;
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append("file", file);
// 업로드 진행률 이벤트
xhr.upload.addEventListener("progress", function (e) {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
document.getElementById("progressBar").value = percent;
document.getElementById("percent").innerText = percent + "%";
}
});
xhr.open("POST", "/upload");
xhr.send(formData);
});
</script>
👉 여기서 xhr.upload.onprogress 이벤트를 활용하면, 업로드 진행 상태를 클라이언트에서 실시간으로 알 수 있어요.
요즘은 XMLHttpRequest(XHR)를 직접 쓰는 경우가 줄었고,
대부분은 fetch API나 그 위에서 동작하는 Axios, SWR, React Query 같은 라이브러리를 많이 씁니다.
다만, 파일 업로드 진행률 같은 경우는 약간 애매해요:
✅ 현재 상황
- fetch API
표준이고 모던하지만 업로드 진행률 이벤트(onprogress)를 지원하지 않음. (다운로드 스트리밍은 지원해요) - XMLHttpRequest
오래됐지만 여전히 업로드 진행률 이벤트 제공 → 그래서 아직도 파일 업로드 바에는 종종 사용됩니다. - Axios
내부적으로 XHR을 써서 onUploadProgress 같은 옵션 제공.
→ 실무에서는 "직접 XHR 쓰는 대신 Axios"를 많이 쓰는 이유가 이거예요.
✅ fetch만으로 하고 싶다면?
업로드가 아니라 다운로드에 대해서는 fetch + ReadableStream으로 진행률 계산이 가능하지만, 업로드는 아직 공식적으로 지원이 없어요. 그래서 브라우저 표준만 쓰고 싶으면 업로드 진행률은 직접 구현하기가 까다롭습니다.
🚀 정리
- 단순히 최신 문법만 고집 → fetch는 업로드 진행률 이벤트가 없어서 진행률 표시 불가
- 진행률까지 필요 → Axios 같은 라이브러리를 쓰거나, 어쩔 수 없이 XMLHttpRequest 사용
- 업로드 후 서버 처리 진행률 → WebSocket / SSE로 서버 상태를 따로 알려줘야 함
XMLHttpRequest(XHR)는 웹 초창기부터 AJAX 통신을 가능하게 한 핵심 API였지만,
지금은 fetch 같은 대체 기술이 나오면서 점점 덜 쓰이는 추세예요. 이유를 정리해보면:
🚫 XHR이 요즘 많이 안 쓰이는 이유
- 복잡하고 가독성 낮음
- 콜백 기반이라 코드가 길어지고, 에러 처리/체이닝이 지저분해집니다.
- 예:
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data");
xhr.onload = function() {
if (xhr.status === 200) {
console.log(xhr.responseText);
}
};
xhr.send();
→ 같은 걸 fetch로 하면:
fetch("/api/data")
.then(res => res.json())
.then(data => console.log(data));
- Promise/async-await 미지원
- XHR은 자체적으로 Promise를 지원하지 않습니다.
- fetch는 기본적으로 Promise 기반이라 async/await 문법과 자연스럽게 맞습니다.
- 스트리밍(Streaming) 한계
- XHR은 응답이 전부 다 와야 responseText로 접근 가능 → 대용량/스트리밍 처리 불편.
- fetch는 ReadableStream을 활용해서 데이터를 조각조각(streaming) 받을 수 있어 성능에 유리합니다.
- 인터페이스가 구식
- 이벤트(onload, onerror, onprogress 등) 위주 API → 모던한 체이닝 스타일에 비해 불편.
- 헤더/응답 처리도 fetch가 훨씬 직관적이고 깔끔합니다.
- 호환성 문제는 거의 사라짐
- 예전에는 IE 지원 때문에 XHR이 필수였지만, 지금은 fetch가 거의 모든 최신 브라우저에서 기본 지원됩니다.
✅ 그런데도 XHR이 아직 쓰이는 경우
- 업로드 진행률(progress) 이벤트
→ fetch는 업로드 이벤트를 제공하지 않음. 그래서 파일 업로드 시 진행률 표시가 필요하면 여전히 XHR이나 Axios(내부적으로 XHR 사용)를 씁니다. - 레거시 코드 유지보수
오래된 프로젝트에는 여전히 XHR 기반 코드가 많습니다.
👉 정리하면,
- XHR은 기능은 강력하지만 문법이 복잡하고 구식
- fetch는 간결하고 모던한 API + Promise/async-await 지원 + 스트리밍 가능
- 단, 업로드 진행률 같은 특정 기능 때문에 XHR이 아직 완전히 사라지진 않음
“콜백 기반”이라는 말은, 비동기 작업이 끝났을 때 실행할 함수를 미리 넘겨주는 방식을 말해요.
📌 콜백 기반의 예시
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data");
xhr.onload = function () {
if (xhr.status === 200) {
console.log("응답:", xhr.responseText);
}
};
xhr.onerror = function () {
console.error("에러 발생");
};
xhr.send();
- getData는 1초 뒤에 데이터를 가져옴
- 데이터가 준비되면 **콜백 함수(callback)**을 호출해서 결과를 알려줌
이런 식으로 “일 끝나면 이 함수 불러줘” 하고 함수를 넘겨주는 게 콜백 기반이에요.
📌 XHR의 콜백 기반 예
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data");
xhr.onload = function () {
if (xhr.status === 200) {
console.log("응답:", xhr.responseText);
}
};
xhr.onerror = function () {
console.error("에러 발생");
};
xhr.send();
- 요청 성공하면 → onload 콜백 실행
- 요청 실패하면 → onerror 콜백 실행
📌 문제점 (콜백 지옥)
콜백을 여러 개 중첩해서 쓰다 보면 코드가 복잡해집니다.
loginUser("id", "pw", function(user) {
getUserProfile(user, function(profile) {
getUserPosts(profile.id, function(posts) {
console.log("결과:", posts);
});
});
});
→ 들여쓰기가 깊어지고, 에러 처리도 중복되면서 유지보수가 힘들어져요. 이걸 흔히 **콜백 지옥(callback hell)**이라고 부릅니다.
📌 Promise / async-await (콜백 대체)
이 문제를 해결하기 위해 등장한 게 Promise와 async/await입니다.
// Promise 체이닝
loginUser("id", "pw")
.then(user => getUserProfile(user))
.then(profile => getUserPosts(profile.id))
.then(posts => console.log("결과:", posts))
.catch(err => console.error(err));
// async/await
async function run() {
try {
const user = await loginUser("id", "pw");
const profile = await getUserProfile(user);
const posts = await getUserPosts(profile.id);
console.log("결과:", posts);
} catch (err) {
console.error(err);
}
}
→ 가독성이 좋아지고, 동기 코드처럼 작성할 수 있어서 훨씬 관리하기 쉽습니다.
👉 정리하면:
- 콜백 기반 = "작업 끝나면 이 함수 불러" 라고 함수를 넘겨주는 방식
- XHR은 이런 콜백 기반 API라서 코드가 길고 복잡해짐
- fetch는 Promise 기반이라 async/await로 훨씬 깔끔하게 쓸 수 있음
프로그래밍에서 **동기(synchronous)**와 **비동기(asynchronous)**는
작업이 실행되고 결과를 기다리는 방식의 차이를 말합니다.
✅ 동기(synchronous)
- 작업을 순서대로 하나씩 실행하는 방식
- 앞의 작업이 끝나야 다음 작업이 실행됨
- 마치 줄 서서 차례차례 처리되는 느낌
예시 (동기)
console.log("1. 주문 받기");
console.log("2. 음식 만들기");
console.log("3. 서빙하기");
실행 결과:
1. 주문 받기
2. 음식 만들기
3. 서빙하기
→ 앞에 게 끝나야 뒤에 게 실행됨.
✅ 비동기(asynchronous)
- 작업이 끝날 때까지 기다리지 않고 다음 작업을 먼저 실행하는 방식
- 시간이 오래 걸리는 작업(예: 서버 요청, 파일 읽기, 타이머)을 기다리는 동안 다른 일을 할 수 있음
- "끝나면 알려줄게(콜백, Promise, async/await)" 이런 개념
예시 (비동기)
console.log("1. 주문 받기");
setTimeout(() => {
console.log("2. 음식 만들기 (3초 걸림)");
}, 3000);
console.log("3. 서빙하기 준비");
실행 결과:
1. 주문 받기 3. 서빙하기 준비 2. 음식 만들기 (3초 걸림)
→ 음식 만드는 동안(3초) 기다리지 않고 서빙 준비를 먼저 실행.
→ 음식이 다 만들어지면(3초 후) 결과가 나옴.
✅ 현실 비유
- 동기: 은행 창구에서 내 차례가 올 때까지 줄 서서 기다림
- 비동기: 번호표 뽑고 기다리는 동안 커피 마시러 갔다가, 내 번호가 불리면 창구로 가는 것
👉 정리:
- 동기: "차례대로, 기다리면서 진행"
- 비동기: "기다리지 않고, 끝나면 알려줌"
2. Fetch + Axios 같은 라이브러리 사용
만약 fetch만 쓰면 진행률 추적이 어렵지만, Axios 같은 라이브러리는 진행률 콜백을 제공합니다.
import axios from "axios";
const fileInput = document.getElementById("fileInput");
fileInput.addEventListener("change", async function () {
const file = this.files[0];
if (!file) return;
const formData = new FormData();
formData.append("file", file);
await axios.post("/upload", formData, {
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
document.getElementById("progressBar").value = percent;
document.getElementById("percent").innerText = percent + "%";
},
});
});
3. 서버 쪽 고려사항
- 업로드는 실제로 서버에 데이터를 전송하는 동안 발생하는 이벤트라서, 서버는 특별히 안 해도 되지만,
업로드 후 처리 진행 상황(예: 압축, 변환 같은 추가 작업)까지 표시하려면 WebSocket/SSE로 서버 상태를 클라이언트에 보내줘야 합니다. - 단순히 업로드 퍼센트만 표시할 거라면 클라이언트 측 이벤트로 충분합니다.
👉 정리:
- 단순 업로드 전송 퍼센트: xhr.upload.onprogress or axios.onUploadProgress
- 업로드 후 서버 작업까지 진행률: 서버 → 클라이언트에 WebSocket/SSE로 진행률 push
async/await는 자바스크립트에서 비동기 코드를 동기 코드처럼 읽기 쉽게 작성할 수 있게 해주는 문법이에요.
✅ 배경
- 예전: 콜백(callback) 기반 → 중첩이 많아지고 가독성이 떨어짐 (콜백 지옥)
- 그 후: Promise 등장 → 체이닝으로 조금 나아졌지만 .then().then().catch()가 길어질 수 있음
- 최신: async/await → 마치 순서대로 실행되는 것처럼 작성 가능
✅ 기본 문법
- async 함수
- 함수 앞에 async를 붙이면, 그 함수는 항상 Promise를 반환합니다.
async function myFunc() {
return 42;
}
myFunc().then(result => console.log(result)); // 42
📌 Promise가 뭔가요?
Promise는 자바스크립트에서 비동기 작업의 **“결과를 담을 약속 객체”**입니다.
즉, 지금은 결과가 없지만,
미래에 **성공하면 값(resolve), 실패하면 이유(reject)**를 알려주는 상자라고 생각하면 돼요.
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("성공!");
// reject("실패..."); // 실패하는 경우
}, 1000);
});
promise.then(result => console.log(result)); // 1초 뒤 "성공!"
📌 async 함수가 Promise를 반환한다는 의미
async function myFunc() {
return 42;
}
- 여기서 return 42;는 그냥 숫자 42를 반환하는 것처럼 보이지만,
- async가 붙어 있기 때문에 자동으로 Promise.resolve(42) 형태로 감싸져서 반환됩니다.
즉, 내부적으로는 이런 거랑 같아요:
function myFunc() {
return Promise.resolve(42);
}
📌 그래서 왜 .then()을 쓸 수 있나?
myFunc().then(result => console.log(result));
- myFunc()는 사실 Promise를 반환합니다. (Promise { 42 })
- 그래서 .then()을 붙여서, 결과값(42)이 준비됐을 때 사용할 수 있는 거예요.
📌 직접 확인해보기
async function myFunc() {
return 42;
}
const result = myFunc();
console.log(result); // Promise { 42 }
👉 콘솔에 찍어보면 Promise 객체가 나옵니다. 숫자 42 자체가 아니라, 42를 담고 있는 Promise예요.
📌 정리
- async 함수는 항상 Promise를 반환합니다.
- return 42;라고 써도 실제로는 Promise.resolve(42)를 반환하는 것과 같음.
- 그래서 myFunc() 호출하면 결과는 42가 아니라 Promise { 42 }.
- .then()이나 await를 써야 실제 값 42를 꺼낼 수 있습니다.
**Promise는 “결과값을 나중에 줄게”라는 약속(상자)**이고,
.then()이나 await은 그 상자를 열어서 실제 값을 꺼내는 방법입니다.
1. .then() 으로 꺼내기
async function myFunc() {
return 42;
}
myFunc().then(result => {
console.log(result); // 👉 42
});
- myFunc() 실행 → Promise { 42 } 반환
- .then() 안의 함수는 Promise가 성공(resolve) 되었을 때 실행됨
- 그래서 result에는 진짜 값 42가 들어옴
2. await 으로 꺼내기
async function myFunc() {
return 42;
}
async function run() {
const result = await myFunc();
console.log(result); // 👉 42
}
run();
- await myFunc() → myFunc()가 반환한 Promise가 끝날 때까지 기다린 뒤 실제 값 42를 반환
- 그래서 result 변수에 바로 42가 들어감
- 단, await은 async 함수 안에서만 사용 가능
3. 직접 눈으로 확인하기
async function myFunc() {
return 42;
}
console.log(myFunc());
// 👉 Promise { 42 } (그냥 호출하면 Promise 상자)
myFunc().then(value => console.log(value));
// 👉 42 (.then()으로 값 꺼내기)
async function run() {
const value = await myFunc();
console.log(value);
}
// 👉 42 (await으로 값 꺼내기)
run();
🚚 택배 박스 비유
- Promise { 42 } = "안에 42라는 물건이 들어있는 택배 상자, 하지만 아직 도착 중일 수도 있음"
- .then() = "택배가 도착하면 열어서 안에 있는 물건(42)을 꺼내 쓰겠다"
- await = "택배가 도착할 때까지 잠깐 기다렸다가, 물건(42)을 꺼내서 바로 쓰겠다"
👉 정리하면:
- myFunc() 자체는 Promise를 반환 → 그냥은 값 못 씀
- .then() 이나 await을 통해 Promise가 resolve된 후 실제 값(42)을 꺼낼 수 있음
지금까지 본 async/await / Promise 예제를 콜백(callback) 방식으로 바꿔서 표현해드릴게요.
📌 원래 async/await 코드
async function myFunc() {
return 42;
}
async function run() {
const result = await myFunc();
console.log(result); // 42
}
run();
📌 같은 동작을 콜백으로 구현하기
콜백 기반에서는 Promise 대신 **“작업이 끝나면 실행할 함수”**를 직접 넘겨야 해요.
function myFunc(callback) {
// 비동기 작업 흉내내기 (예: 서버 응답)
setTimeout(() => {
callback(42); // 일이 끝나면 callback 실행, 결과 전달
}, 1000);
}
function run() {
myFunc(function(result) {
console.log(result); // 42
});
}
run();
📌 차이점
- 콜백 방식
- myFunc(callback) 호출 시, 바로 결과를 반환하지 않고
- 일이 끝나면 → 내가 넘긴 callback 함수를 불러줌
- 그래서 결과는 callback 안에서만 사용할 수 있음
- Promise/async/await 방식
- myFunc()가 Promise { 42 }라는 “결과 상자”를 반환
- .then()이나 await으로 결과를 꺼낼 수 있음
- 코드가 동기적으로 읽혀서 훨씬 깔끔함
📌 흐름 비교
콜백
myFunc(callback)
└──> setTimeout 1초 후 → callback(42)
Promise/await
myFunc() → Promise { 42 }
await myFunc() → Promise resolve → 42
👉 정리:
- callback = “일 끝나면 내가 준 함수를 실행해줘”
- Promise/await = “결과가 담긴 상자(Promise)를 받고, 다 끝나면 꺼내 쓰자”
콜백 기반 예제에서 setTimeout(() => { callback(42); }, 1000)에서 말하는 **“일이 끝났다”**는
바로 1초 기다리는 것이에요.
하나씩 풀어보면
function myFunc(callback) {
setTimeout(() => {
callback(42); // 1초 후 결과 전달
}, 1000);
}
- myFunc가 호출되면
- 바로 결과(42)를 반환하지 않고, 내부에서 setTimeout을 걸어요.
- setTimeout은 1초 후 실행되도록 예약만 해놓는 함수예요.
- 1초 동안 자바스크립트는 다른 코드 실행 가능
- 이게 바로 비동기(asynchronous) 특성입니다.
- 즉, 기다리는 동안 UI가 멈추지 않고 다른 작업도 할 수 있음
- 1초 후 setTimeout 내부 코드 실행
- 여기서 **“일이 끝났다”**라고 보면 됨
- 그러면 우리가 미리 넘겨둔 callback 함수가 호출되고, 결과 42를 받음
myFunc(function(result) {
console.log(result); // 42 출력
});
- 1초 후에 콘솔에 42가 출력되는 이유는
→ 1초 기다린 뒤 callback 함수 실행되었기 때문이에요.
요약
- 콜백에서 말하는 “일이 끝났다” = 비동기 작업이 완료된 시점
- 예제에서는 **1초 기다림(setTimeout)**이 그 비동기 작업
- 실제 서버 요청, 파일 읽기, 업로드 등도 동일한 원리로,
“응답이 오면 콜백을 호출”하는 구조예요
- await 키워드
- async 함수 안에서만 사용 가능
- Promise가 처리될 때까지 기다렸다가, 결과값을 반환
- 실행 흐름은 잠깐 "멈춘 것처럼" 보이지만, 실제로는 다른 작업은 계속 돌아감 (비동기 유지
async function getData() { const response = await fetch("https://jsonplaceholder.typicode.com/posts/1"); const data = await response.json(); console.log(data); } getData();
✅ Promise vs async/await 비교
Promise 스타일
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(data => {
console.log("데이터:", data);
})
.catch(err => console.error(err));
async/await 스타일
async function loadPost() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
const data = await response.json();
console.log("데이터:", data);
} catch (err) {
console.error(err);
}
}
loadPost();
→ 기능은 같지만, 동기 코드처럼 읽히고 가독성이 훨씬 좋아짐
✅ 장점
- 동기 코드처럼 직관적이라 가독성 ↑
- try/catch로 에러 처리 가능
- 콜백 지옥/Promise 체이닝 문제 해결
✅ 단점
- 병렬 실행할 때는 Promise.all() 같은 걸 활용해야 더 효율적
// 순차 실행 (비효율적)
const a = await fetch("/a");
const b = await fetch("/b");
// 병렬 실행 (효율적)
const [a, b] = await Promise.all([fetch("/a"), fetch("/b")]);
👉 정리하면:
- async = 함수가 Promise 반환하게 만듦
- await = Promise 결과 나올 때까지 기다림
- 결과적으로 비동기 코드를 동기처럼 깔끔하게 작성할 수 있음
'네트워크2' 카테고리의 다른 글
| HTTP :: 요청 헤더 / 응답 헤더 (0) | 2025.10.07 |
|---|---|
| 쿠키(Cookie) 와 세션(Sessoion) (0) | 2025.10.07 |
| CSRF 란? :: 쿠키 종류(세션 쿠키 vs 영속 쿠키) :: 쿠키 속성 Domain, Path, SameSite, HttpOnly, Expires/Max-Age (0) | 2025.10.07 |
| HTTP 요청/응답 (서버와 통신) :: XHR vs fetch vs Axios (0) | 2025.10.04 |
| 쿼리 파라미터 ?key=value&key=value :: 웹 서버에서 처리 과정 (0) | 2025.10.03 |
| 불법 사이트 안 걸리는 이유 vs 걸리는 이유 (0) | 2025.10.03 |
| 도메인 구매 후 DNS 서버에 등록 절차 (0) | 2025.10.03 |
| 불법 사이트들이 도메인을 바꾸는 방식 (0) | 2025.10.03 |