티스토리 뷰

현대 웹 애플리케이션은 '기다림'의 연속입니다. API 서버에서 데이터를 가져오고, 대용량 이미지를 로드하며, 사용자의 입력에 실시간으로 반응해야 하죠. 하지만 자바스크립트는 **싱글 스레드(Single Thread)**로 동작합니다. 한 번에 하나의 일만 처리할 수 있는 이 언어가 어떻게 끊김 없는 사용자 경험을 만들어낼까요?
그 중심에는 비동기 처리의 진화가 있습니다. 과거의 콜백(Callback) 패턴이 가져온 '지옥'을 지나, 이제는 동기 코드처럼 읽히는 비동기 코드를 작성하는 시대에 도달했습니다.
1. Deep Dive: 왜 비동기를 제어해야 하는가?
자바스크립트 엔진은 비동기 작업을 만나면 이를 브라우저나 Node.js의 **Web APIs(또는 Libuv)**로 위임합니다. 작업이 완료되면 결과값이 **태스크 큐(Task Queue)**에 쌓이고, 이벤트 루프에 의해 메인 스레드로 돌아오죠.
문제는 이 '결과값'이 돌아오는 시점을 예측할 수 없다는 것입니다.
비유로 이해하는 비동기 제어
맛집에 가서 줄을 서는 상황을 상상해 보세요.
- Callback: 점원이 "자리가 나면 제가 직접 당신을 찾아가서 데려올게요"라고 말합니다. 그런데 자리가 나기 전에 화장실을 가거나 전화를 받으러 가면 점원은 당신을 찾지 못해 혼란에 빠집니다. (제어권 상실)
- Promise: 점원이 진동벨을 줍니다. 당신은 벨을 쥐고 카페에 가거나 산책을 할 수 있습니다. 벨이 울리면(Fulfilled) 음식을 받고, 재료가 떨어지면 빨간 불(Rejected)이 들어옵니다. (상태 중심 제어)
- async/await: 진동벨을 들고 있지만, 마치 음식이 바로 나올 것처럼 식탁 앞에 앉아서 기다리는 것처럼 코드를 작성합니다. 하지만 실제로 당신의 몸이 굳어있는 건 아니죠. (동기적 표현)
2. Hands-on: 실전 이커머스 결제 로직 구현
단순한 setTimeout 예제가 아닌, 재고 확인 -> 결제 처리 -> 이메일 발송으로 이어지는 실제 비즈니스 파이프라인을 구축해 보겠습니다.
Step 1: Promise 기반의 워크플로우
const checkInventory = (productId) => {
return new Promise((resolve, reject) => {
console.log("📦 재고 확인 중...");
setTimeout(() => {
const isAvailable = true; // 실제 DB 조회 로직 대체
isAvailable ? resolve({ productId, price: 50000 }) : reject(new Error("품절된 상품입니다."));
}, 1000);
});
};
const processPayment = (orderInfo) => {
return new Promise((resolve, reject) => {
console.log(`💳 ${orderInfo.price}원 결제 진행 중...`);
setTimeout(() => {
const success = Math.random() > 0.2; // 80% 확률로 결제 성공
success ? resolve({ ...orderInfo, status: "PAID" }) : reject(new Error("결제 승인 거절"));
}, 1500);
});
};
const sendConfirmation = (orderInfo) => {
return new Promise((resolve) => {
console.log("📧 결제 완료 이메일 발송 중...");
setTimeout(() => resolve(`Order ${orderInfo.productId} 완료!`), 500);
});
};
Step 2: async/await를 이용한 세련된 합성
위의 Promise들을 연쇄적으로 호출할 때, then 체이닝보다 async/await가 가독성 측면에서 압도적입니다.
/**
* 결제 파이프라인 실행 함수
* @param {string} productId
*/
async function completePurchase(productId) {
try {
// 1. 재고 확인 (await를 통한 결과값 변수 할당)
const item = await checkInventory(productId);
// 2. 결제 처리 (이전 단계의 결과값을 인자로 전달)
const receipt = await processPayment(item);
// 3. 이메일 발송
const result = await sendConfirmation(receipt);
console.log("✅ 최종 결과:", result);
} catch (error) {
// 통합 에러 핸들링: 어느 단계에서 발생하든 이곳으로 수렴합니다.
handleError(error);
} finally {
console.log("🔄 트랜잭션 종료");
}
}
function handleError(error) {
console.error("❌ 처리 실패:", error.message);
// 서비스 특성에 따른 로깅이나 사용자 알림 로직
}
completePurchase("MACBOOK_PRO_2026");
3. Troubleshooting: 자주 겪는 실수들
병렬 처리의 누락 (The Waterfall Trap)
여러 개의 독립적인 API 호출을 할 때, 무심코 await를 순차적으로 나열하면 성능 저하가 발생합니다.
- Bad: 첫 번째 데이터 로드가 끝날 때까지 두 번째 로드가 시작되지 않음.
- Good: Promise.all을 사용하여 동시 처리.
// 두 명의 사용자 정보를 가져올 때 (서로 연관 없음)
async function fetchUsers(id1, id2) {
// 동시에 시작하여 가장 늦게 끝나는 작업 시간에 수렴함
const [user1, user2] = await Promise.all([
fetch(`/api/user/${id1}`),
fetch(`/api/user/${id2}`)
]);
}
에러 삼킴 (Error Swallowing)
async 함수 내부에서 try...catch 없이 비동기 함수를 호출하고 이를 적절히 반환하지 않으면, 에러가 전역으로 퍼지거나 조용히 사라져 디버깅이 불가능해집니다. 항상 에러 경계를 설정하세요.
4. Trade-offs: 무엇을 선택할 것인가?
| 특성 | Promise (.then) | async / await |
| 가독성 | 중첩 시 복잡도가 올라감 | 동기 코드와 유사하여 직관적임 |
| 에러 처리 | .catch() 로 처리 | try...catch 로 처리 |
| 제어 흐름 | 분기 처리가 까다로움 | if, for 문 등 내장 제어문 활용 용이 |
| 디버깅 | 콜 스택 확인이 어려울 수 있음 | 중단점(Breakpoint) 설정이 용이함 |
하지만 주의하세요. async/await가 모든 것을 해결하지는 않습니다. 자바스크립트의 비동기는 여전히 이벤트 루프를 기반으로 합니다. 만약 루프 내부에서 CPU 집약적인 연산($O(n^2)$ 이상의 복잡도를 가진 로직 등)을 수행한다면, await 여부와 관계없이 메인 스레드는 차단됩니다.
결론: 비동기는 기술이 아니라 '흐름'이다
비동기 프로그래밍의 핵심은 단순히 문법을 익히는 것이 아니라, **"지금 실행되지 않아도 될 것"**과 **"반드시 순서대로 실행되어야 하는 것"**을 구분하는 설계 능력에 있습니다.
Promise는 비동기 작업의 상태를 객체화했고, async/await는 그 객체를 인간의 언어에 가깝게 표현할 수 있게 해주었습니다. 이제 여러분의 코드에서 콜백의 흔적을 지우고, 견고한 비동기 파이프라인을 구축해 보시기 바랍니다.
'Frontend > JAVASCRIPT' 카테고리의 다른 글
| 자바스크립트의 심장, 실행 컨텍스트와 호이스팅: 코드의 흐름을 지배하는 내부 메커니즘 (0) | 2026.03.02 |
|---|---|
| 자바스크립트의 유령, 클로저(Closure)와 스코프: 메모리 속에 살아있는 변수의 비밀 (0) | 2026.03.02 |
| 자바스크립트의 그림자, 콜백 지옥(Callback Hell)을 우아하게 탈출하는 법 (0) | 2026.03.02 |
| 동적인 웹을 위한 첫걸음: DOM 조작의 본질과 효율적인 제어 전략 (0) | 2026.03.02 |
| 유연한 데이터 핸들링의 정점: Modern JavaScript 객체 분해와 확산 연산자 활용법 (0) | 2026.03.02 |
- Total
- Today
- Yesterday
- LLM
- 구글
- CSS
- AI
- react
- Javascript
- prompt engineering
- TypeScript
- HBM
- It용어
- CSR
- 엣지컴퓨팅
- MSA
- Rag
- HTML
- 스마트안경
- sLLM
- 웹기초
- Nextjs
- 카카오
- 협력
- java
- on-device ai
- SSR
- 멀티모달
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |