🔍 1. 동기 vs 비동기가 뭔가요?
동기 처리 (Synchronous):
console.log("1번");
console.log("2번");
console.log("3번");
// 결과: 1번 → 2번 → 3번 (순서대로)
// 앞의 작업이 끝나야 다음 작업 실행
비동기 처리 (Asynchronous):
console.log("1번");
setTimeout(() => {
console.log("2번 (3초 후)");
}, 3000);
console.log("3번");
// 결과: 1번 → 3번 → (3초 후) 2번
// 2번을 기다리지 않고 3번 먼저 실행!
⏰ 2. 왜 비동기가 필요할까?
// 만약 동기적으로만 처리한다면...
console.log("서버에 데이터 요청 시작");
// 여기서 3초 동안 멈춤... (서버 응답 기다림)
// 사용자는 3초 동안 아무것도 할 수 없음!
console.log("데이터 받음");
console.log("화면에 버튼 클릭 처리");
// 비동기로 처리하면...
console.log("서버에 데이터 요청 시작");
console.log("화면에 버튼 클릭 처리"); // 바로 실행!
// 서버 응답이 오면 그때 처리
🔄 3. 비동기 처리의 3가지 방법
방법 1: 콜백 (Callback) – 구식
function getData(callback) {
setTimeout(() => {
const data = "서버에서 받은 데이터";
callback(data); // 작업 완료 후 콜백 호출
}, 2000);
}
getData((result) => {
console.log(result); // "서버에서 받은 데이터"
});
console.log("이건 먼저 실행됨");
콜백의 문제점 – 콜백 지옥:
// 단계별 작업이 필요한 경우
getUserData((user) => {
getOrderData(user.id, (orders) => {
getPaymentData(orders[0].id, (payment) => {
processPayment(payment, (result) => {
console.log("결제 완료!");
// 중첩이 너무 깊어짐... 😵
});
});
});
});
방법 2: Promise – 개선된 방법
function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve("서버에서 받은 데이터"); // 성공
} else {
reject("에러 발생!"); // 실패
}
}, 2000);
});
}
// Promise 사용법
getData()
.then((result) => {
console.log(result); // 성공했을 때
})
.catch((error) => {
console.log(error); // 실패했을 때
});
Promise 체이닝:
getUserData()
.then((user) => {
return getOrderData(user.id);
})
.then((orders) => {
return getPaymentData(orders[0].id);
})
.then((payment) => {
return processPayment(payment);
})
.then((result) => {
console.log("결제 완료!");
})
.catch((error) => {
console.log("어디선가 에러:", error);
});
방법 3: async/await – 최신 방법 (제일 좋음)
async function processOrder() {
try {
const user = await getUserData();
const orders = await getOrderData(user.id);
const payment = await getPaymentData(orders[0].id);
const result = await processPayment(payment);
console.log("결제 완료!");
// 마치 동기 코드처럼 보이지만 비동기!
} catch (error) {
console.log("에러:", error);
}
}
processOrder();
🌐 4. fetch API로 실제 사용해보기
기본 fetch 사용법:
// Promise 방식
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.log('에러:', error);
});
// async/await 방식 (더 깔끔)
async function getPost() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const data = await response.json();
console.log(data);
} catch (error) {
console.log('에러:', error);
}
}
getPost();
여러 API 동시에 호출:
async function getMultipleData() {
try {
// 동시에 여러 요청 보내기
const [usersResponse, postsResponse] = await Promise.all([
fetch('https://jsonplaceholder.typicode.com/users'),
fetch('https://jsonplaceholder.typicode.com/posts')
]);
const users = await usersResponse.json();
const posts = await postsResponse.json();
console.log('사용자:', users);
console.log('게시글:', posts);
} catch (error) {
console.log('에러:', error);
}
}
POST 요청 보내기:
async function createPost() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: '새 게시글',
body: '게시글 내용',
userId: 1
})
});
const newPost = await response.json();
console.log('생성된 게시글:', newPost);
} catch (error) {
console.log('에러:', error);
}
}
⚠️ 5. 자주 하는 실수들
실수 1: await 없이 사용
// ❌ 잘못된 방법
async function badExample() {
const response = fetch('https://api.example.com/data');
console.log(response); // Promise 객체가 출력됨 (데이터 아님!)
}
// ✅ 올바른 방법
async function goodExample() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data); // 실제 데이터 출력
}
실수 2: 에러 처리 안 함
// ❌ 위험한 방법
async function risky() {
const response = await fetch('https://wrong-url.com/data');
const data = await response.json(); // 에러 발생 시 앱 크래시!
}
// ✅ 안전한 방법
async function safe() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.log('데이터를 가져올 수 없습니다:', error);
return null;
}
}
실수 3: 순차 처리 vs 병렬 처리 혼동
// ❌ 느린 방법 (순차 처리)
async function slow() {
const user = await fetch('/api/user'); // 1초 기다림
const orders = await fetch('/api/orders'); // 또 1초 기다림
// 총 2초 소요
}
// ✅ 빠른 방법 (병렬 처리)
async function fast() {
const [userResponse, ordersResponse] = await Promise.all([
fetch('/api/user'),
fetch('/api/orders')
]);
// 동시에 요청해서 1초만 소요
}
🎯 6. 실제 사용 예시
class WeatherApp {
constructor() {
this.apiKey = 'your-api-key';
this.baseUrl = 'https://api.openweathermap.org/data/2.5';
}
async getCurrentWeather(city) {
try {
this.showLoading();
const response = await fetch(
`${this.baseUrl}/weather?q=${city}&appid=${this.apiKey}&units=metric`
);
if (!response.ok) {
throw new Error(`날씨 정보를 찾을 수 없습니다: ${response.status}`);
}
const weatherData = await response.json();
this.displayWeather(weatherData);
} catch (error) {
this.showError(error.message);
} finally {
this.hideLoading();
}
}
showLoading() {
console.log('날씨 정보 로딩 중...');
}
hideLoading() {
console.log('로딩 완료');
}
displayWeather(data) {
console.log(`${data.name}: ${data.main.temp}°C, ${data.weather[0].description}`);
}
showError(message) {
console.log('에러:', message);
}
}
// 사용
const weatherApp = new WeatherApp();
weatherApp.getCurrentWeather('Seoul');
💡 핵심 정리:
1. 비동기가 필요한 이유:
- 네트워크 요청, 파일 읽기 등 시간이 걸리는 작업
- 사용자 경험 향상 (멈추지 않는 앱)
2. async/await가 최고:
- 코드가 읽기 쉬움
- 에러 처리가 간단 (try/catch)
- Promise의 장점 + 동기 코드 같은 가독성
3. 꼭 기억할 것:
await
는async
함수 안에서만 사용- 에러 처리는 필수 (
try/catch
) - 병렬 처리가 필요하면
Promise.all()
사용
4. fetch API 패턴:
async function apiCall() {
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network error');
const data = await response.json();
return data;
} catch (error) {
console.log('Error:', error);
returnnull;
}
}