6장: 비동기 처리
📋 강의 개요
- 소요 시간: 약 8-10시간
- 난이도: ⭐⭐⭐ 중급~고급
- 선수 지식: JavaScript 기초, 함수, 콜백
- 학습 목표: 비동기 프로그래밍의 개념을 이해하고 Promise, async/await을 활용할 수 있다.
📚 6.1 동기 vs 비동기
강의 포인트
- JavaScript는 싱글 스레드
- 동기는 순차 실행, 비동기는 병렬 실행
- 비동기가 필요한 이유
동기 (Synchronous) 실행
console.log('1. 첫 번째');
console.log('2. 두 번째');
console.log('3. 세 번째');
// 출력:
// 1. 첫 번째
// 2. 두 번째
// 3. 세 번째
// → 순서대로 실행
특징:
- 코드가 순서대로 실행
- 이전 작업이 끝나야 다음 작업 실행
- 오래 걸리는 작업이 있으면 멈춤 (Blocking)
비동기 (Asynchronous) 실행
console.log('1. 시작');
setTimeout(() => {
console.log('2. 2초 후 실행');
}, 2000);
console.log('3. 끝');
// 출력:
// 1. 시작
// 3. 끝
// 2. 2초 후 실행 (2초 후)
// → 순서가 바뀜!
특징:
- 오래 걸리는 작업을 기다리지 않음 (Non-blocking)
- 작업이 끝나면 콜백 함수 실행
- 사용자 경험 향상
비동기가 필요한 경우
// 1. 서버 통신 (시간이 오래 걸림)
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data));
// 2. 타이머
setTimeout(() => console.log('1초 후'), 1000);
// 3. 파일 읽기/쓰기
readFile('data.txt', (data) => console.log(data));
// 4. 사용자 입력 대기
button.addEventListener('click', () => console.log('클릭!'));
이벤트 루프 (Event Loop)
Call Stack (호출 스택)
↓
현재 실행 중인 함수
↓
Web APIs (setTimeout, fetch 등)
↓
Task Queue (작업 대기열)
↓
Event Loop (끊임없이 확인)
↓
Call Stack이 비면 Queue에서 가져옴
📚 6.2-6.3 콜백 함수와 콜백 지옥
강의 포인트
- 콜백은 “나중에 실행될 함수”
- 콜백 지옥은 가독성이 나쁨
- Promise가 해결책
콜백 함수 기본
// 콜백 함수 = 다른 함수에 인자로 전달되는 함수
function greeting(name, callback) {
console.log(`안녕, ${name}`);
callback();
}
greeting('홍길동', function() {
console.log('콜백 실행!');
});
// 출력:
// 안녕, 홍길동
// 콜백 실행!
비동기 콜백
console.log('시작');
setTimeout(function() {
console.log('1초 후 실행');
}, 1000);
console.log('끝');
// 출력:
// 시작
// 끝
// 1초 후 실행 (1초 후)
콜백 지옥 (Callback Hell) 😱
// 사용자 정보 → 게시글 목록 → 첫 번째 게시글 → 댓글 목록
getUser(userId, function(user) {
getPosts(user.id, function(posts) {
getPost(posts[0].id, function(post) {
getComments(post.id, function(comments) {
console.log(comments);
// 계단식 구조... 읽기 어려움 😢
});
});
});
});
문제점:
- 가독성이 나쁨 (피라미드 구조)
- 에러 처리가 어려움
- 유지보수가 어려움
- 디버깅이 어려움
콜백 에러 처리
function loadData(callback) {
setTimeout(() => {
const error = Math.random() > 0.5 ? new Error('에러 발생!') : null;
const data = { name: '홍길동' };
// Node.js 스타일 콜백 (error-first)
callback(error, data);
}, 1000);
}
loadData((err, data) => {
if (err) {
console.error('에러:', err.message);
return;
}
console.log('데이터:', data);
});
📚 6.4-6.6 Promise
강의 포인트
- Promise는 비동기 작업의 최종 결과를 나타내는 객체
- 3가지 상태: pending, fulfilled, rejected
- then/catch로 결과 처리
Promise 기본
// Promise 생성
const promise = new Promise((resolve, reject) => {
// 비동기 작업
setTimeout(() => {
const success = true;
if (success) {
resolve('성공!'); // 성공 시
} else {
reject('실패!'); // 실패 시
}
}, 1000);
});
// Promise 사용
promise
.then(result => {
console.log(result); // "성공!"
})
.catch(error => {
console.error(error); // "실패!"
})
.finally(() => {
console.log('완료'); // 성공/실패 관계없이 실행
});
Promise의 3가지 상태
// 1. Pending (대기) - 초기 상태
const promise1 = new Promise((resolve, reject) => {
// 아직 resolve/reject 안 함
});
// 2. Fulfilled (이행) - 성공
const promise2 = new Promise((resolve, reject) => {
resolve('성공!');
});
// 3. Rejected (거부) - 실패
const promise3 = new Promise((resolve, reject) => {
reject('실패!');
});
Promise 체이닝
// 콜백 지옥을 Promise로 해결!
getUser(userId)
.then(user => getPosts(user.id))
.then(posts => getPost(posts[0].id))
.then(post => getComments(post.id))
.then(comments => console.log(comments))
.catch(error => console.error(error));
// 훨씬 읽기 쉬움! ✨
Promise 체이닝 상세
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
})
.then(result => {
console.log(result); // 1
return result * 2;
})
.then(result => {
console.log(result); // 2
return result * 2;
})
.then(result => {
console.log(result); // 4
});
// 출력:
// 1 (1초 후)
// 2
// 4
Promise 에러 처리
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('HTTP 에러');
}
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
// 모든 에러를 여기서 처리
console.error('에러 발생:', error);
})
.finally(() => {
console.log('완료');
});
Promise.all() - 모든 Promise 완료 대기
const promise1 = fetch('https://api.example.com/user/1');
const promise2 = fetch('https://api.example.com/user/2');
const promise3 = fetch('https://api.example.com/user/3');
// 모든 Promise가 완료될 때까지 대기
Promise.all([promise1, promise2, promise3])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(users => {
console.log(users); // [user1, user2, user3]
})
.catch(error => {
// 하나라도 실패하면 catch 실행
console.error(error);
});
특징:
- 모든 Promise가 성공해야 성공
- 하나라도 실패하면 즉시 실패
- 결과는 배열로 반환 (순서 유지)
Promise.race() - 가장 빠른 Promise만
const promise1 = new Promise(resolve => setTimeout(() => resolve('1초'), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('2초'), 2000));
const promise3 = new Promise(resolve => setTimeout(() => resolve('3초'), 3000));
Promise.race([promise1, promise2, promise3])
.then(result => {
console.log(result); // "1초" (가장 빠른 것만)
});
사용 예: 타임아웃 구현
function fetchWithTimeout(url, timeout = 5000) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('타임아웃')), timeout)
)
]);
}
Promise.allSettled() - 모든 결과 확인
const promises = [
Promise.resolve('성공 1'),
Promise.reject('실패'),
Promise.resolve('성공 2')
];
Promise.allSettled(promises)
.then(results => {
console.log(results);
// [
// { status: 'fulfilled', value: '성공 1' },
// { status: 'rejected', reason: '실패' },
// { status: 'fulfilled', value: '성공 2' }
// ]
});
특징:
- 모든 Promise 완료까지 대기
- 성공/실패 관계없이 모든 결과 반환
Promise.any() - 첫 번째 성공한 Promise
const promises = [
Promise.reject('실패 1'),
Promise.resolve('성공!'),
Promise.reject('실패 2')
];
Promise.any(promises)
.then(result => {
console.log(result); // "성공!"
});
📚 6.7-6.8 async / await
강의 포인트
- async/await은 Promise를 더 쉽게 사용하는 문법
- 동기 코드처럼 보이지만 비동기로 동작
- try-catch로 에러 처리
async 함수 기본
// async 함수는 항상 Promise를 반환
async function myFunction() {
return '안녕';
}
myFunction().then(result => console.log(result)); // "안녕"
// 위와 동일
function myFunction2() {
return Promise.resolve('안녕');
}
await - Promise 완료 대기
async function fetchData() {
// await은 Promise가 완료될 때까지 대기
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
}
// Promise 체이닝과 비교
function fetchData2() {
return fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data));
}
순차 실행 vs 병렬 실행
// ❌ 순차 실행 (느림)
async function sequential() {
const user1 = await fetchUser(1); // 1초
const user2 = await fetchUser(2); // 1초
const user3 = await fetchUser(3); // 1초
// 총 3초 걸림
}
// ✅ 병렬 실행 (빠름)
async function parallel() {
const [user1, user2, user3] = await Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3)
]);
// 총 1초 걸림 (동시 실행)
}
try-catch 에러 처리
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('HTTP 에러');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('에러 발생:', error.message);
} finally {
console.log('완료');
}
}
실전 예제
// 사용자 정보 → 게시글 목록 → 댓글 목록
async function getUserData(userId) {
try {
// 순차 실행 (의존 관계가 있음)
const user = await getUser(userId);
const posts = await getPosts(user.id);
const firstPost = await getPost(posts[0].id);
const comments = await getComments(firstPost.id);
return {
user,
posts,
firstPost,
comments
};
} catch (error) {
console.error('에러:', error);
throw error;
}
}
// 사용
getUserData(1)
.then(data => console.log(data))
.catch(error => console.error(error));
async/await 팁
1. forEach에서는 await가 작동 안 함!
// ❌ 작동 안 함
async function processItems(items) {
items.forEach(async (item) => {
await processItem(item); // 대기 안 함!
});
}
// ✅ for...of 사용
async function processItems(items) {
for (const item of items) {
await processItem(item); // 정상 대기
}
}
// ✅ Promise.all 사용 (병렬)
async function processItems(items) {
await Promise.all(
items.map(item => processItem(item))
);
}
2. Top-level await (최신)
// ES2022부터 모듈에서 가능
const data = await fetch('https://api.example.com/data');
console.log(data);
📚 6.9-6.10 Fetch API
강의 포인트
- Fetch API는 HTTP 요청을 위한 최신 API
- Promise 기반
- XMLHttpRequest보다 간결하고 강력
기본 GET 요청
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
// async/await 버전
async function getUsers() {
try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
POST 요청
async function createUser(userData) {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error('HTTP 에러: ' + response.status);
}
const data = await response.json();
return data;
} catch (error) {
console.error('에러:', error);
throw error;
}
}
// 사용
createUser({
name: '홍길동',
email: 'hong@example.com'
}).then(user => console.log('생성됨:', user));
PUT 요청 (전체 수정)
async function updateUser(userId, userData) {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
return response.json();
}
PATCH 요청 (부분 수정)
async function patchUser(userId, updates) {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
});
return response.json();
}
DELETE 요청
async function deleteUser(userId) {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'DELETE'
});
if (response.ok) {
console.log('삭제 완료');
}
}
Response 객체
const response = await fetch('https://api.example.com/users');
// 상태 확인
console.log(response.ok); // true (200-299)
console.log(response.status); // 200
console.log(response.statusText); // "OK"
// 헤더
console.log(response.headers.get('Content-Type'));
// 본문 파싱
const json = await response.json(); // JSON
const text = await response.text(); // 텍스트
const blob = await response.blob(); // Blob (파일)
const buffer = await response.arrayBuffer(); // ArrayBuffer
에러 처리
async function fetchData(url) {
try {
const response = await fetch(url);
// fetch는 HTTP 에러에서 reject 안 됨!
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
// 네트워크 에러 또는 위에서 throw한 에러
console.error('Fetch 에러:', error.message);
throw error;
}
}
요청 옵션
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({ data: 'value' }),
mode: 'cors', // cors, no-cors, same-origin
credentials: 'include', // include, same-origin, omit
cache: 'no-cache', // default, no-cache, reload, force-cache
redirect: 'follow', // follow, error, manual
referrerPolicy: 'no-referrer'
});
📚 6.11 FormData 전송
파일 업로드
const form = document.querySelector('#uploadForm');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
// 추가 데이터
formData.append('userId', '123');
try {
const response = await fetch('/upload', {
method: 'POST',
body: formData // Content-Type 자동 설정
});
const result = await response.json();
console.log('업로드 완료:', result);
} catch (error) {
console.error('업로드 실패:', error);
}
});
📚 6.12-6.13 AJAX와 axios
XMLHttpRequest (구식)
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onload = function() {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.onerror = function() {
console.error('에러');
};
xhr.send();
axios 라이브러리 ⭐
// CDN
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
// GET
axios.get('https://api.example.com/users')
.then(response => console.log(response.data))
.catch(error => console.error(error));
// POST
axios.post('https://api.example.com/users', {
name: '홍길동',
email: 'hong@example.com'
})
.then(response => console.log(response.data));
// async/await
async function getUsers() {
try {
const response = await axios.get('https://api.example.com/users');
console.log(response.data);
} catch (error) {
console.error(error.response.data);
}
}
axios 장점:
- 자동 JSON 변환
- 요청/응답 인터셉터
- 진행 상황 추적
- 요청 취소 가능
- 타임아웃 설정 쉬움
🎯 강의 진행 팁
1-2교시: 동기/비동기, 콜백 (2시간)
- 개념 설명
- 콜백 지옥 체험
3-4교시: Promise (2시간)
- Promise 기초
- 체이닝
- 실습: Promise로 콜백 지옥 해결
5-6교시: async/await (2시간)
- async/await 문법
- 에러 처리
- 실습: API 호출
7-8교시: Fetch API (2시간)
- GET/POST/PUT/DELETE
- 종합 실습: REST API 연동
📝 종합 프로젝트: 게시판 앱
// 사용자 API
class UserAPI {
static async getUsers() {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
return response.json();
}
static async getUser(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
return response.json();
}
}
// 게시글 API
class PostAPI {
static async getPosts() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
return response.json();
}
static async createPost(post) {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(post)
});
return response.json();
}
}
// 사용
async function displayPosts() {
try {
const posts = await PostAPI.getPosts();
posts.slice(0, 10).forEach(post => {
console.log(`${post.id}. ${post.title}`);
});
} catch (error) {
console.error('에러:', error);
}
}
6장 비동기 처리를 완료했습니다! ⏱️