코드가 순차적으로 실행되며, 이전 작업이 완료될 때까지 다음 작업이 대기합니다.
console.log("1번 작업");
console.log("2번 작업");
console.log("3번 작업");
// 출력: 1번 작업 → 2번 작업 → 3번 작업 (순서대로)시간이 걸리는 작업을 백그라운드에서 처리하고, 다른 코드를 계속 실행합니다.
console.log("1번 작업");
setTimeout(() => {
console.log("2번 작업 (1초 후)");
}, 1000);
console.log("3번 작업");
// 출력: 1번 작업 → 3번 작업 → 2번 작업 (1초 후)// ❌ 동기 방식 (가상 코드 - 실제로는 fetch가 비동기)
const data = fetchDataFromServer(); // 3초 걸림
console.log("데이터를 받았습니다."); // 3초 동안 대기
// ✅ 비동기 방식
fetchDataFromServer().then(data => {
console.log("데이터를 받았습니다.");
});
console.log("다른 작업 계속 진행..."); // 즉시 실행비동기 작업의 가장 기본적인 패턴입니다.
// setTimeout - 일정 시간 후 실행
setTimeout(() => {
console.log("1초 후 실행");
}, 1000);
// 콜백 함수 예제
function fetchUser(id, callback) {
setTimeout(() => {
const user = { id, name: "김철수" };
callback(user);
}, 1000);
}
fetchUser(1, (user) => {
console.log(user); // { id: 1, name: "김철수" }
});콜백이 중첩되면 코드가 복잡해집니다.
// 사용자 정보 → 게시글 정보 → 댓글 정보 순차 조회
fetchUser(1, (user) => {
console.log("사용자:", user);
fetchPosts(user.id, (posts) => {
console.log("게시글:", posts);
fetchComments(posts[0].id, (comments) => {
console.log("댓글:", comments);
// 너무 깊게 중첩됨... 😱
});
});
});콜백 지옥을 해결하기 위한 패턴입니다.
Promise는 3가지 상태를 가집니다:
- Pending (대기): 아직 완료되지 않은 상태
- Fulfilled (이행): 성공적으로 완료된 상태
- Rejected (거부): 실패한 상태
// 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);
});function fetchUser(id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id, name: "김철수" });
}, 1000);
});
}
function fetchPosts(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: 1, title: "첫 번째 글" },
{ id: 2, title: "두 번째 글" }
]);
}, 1000);
});
}
// ✅ Promise 체이닝으로 깔끔하게 처리
fetchUser(1)
.then((user) => {
console.log("사용자:", user);
return fetchPosts(user.id);
})
.then((posts) => {
console.log("게시글:", posts);
return posts[0];
})
.then((firstPost) => {
console.log("첫 번째 게시글:", firstPost);
})
.catch((error) => {
console.error("에러 발생:", error);
});// Promise.resolve - 즉시 성공한 Promise 생성
const resolved = Promise.resolve(42);
resolved.then(value => console.log(value)); // 42
// Promise.reject - 즉시 실패한 Promise 생성
const rejected = Promise.reject("에러");
rejected.catch(error => console.log(error)); // "에러"
// Promise.all - 모든 Promise가 성공해야 성공
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then((results) => {
console.log(results); // [1, 2, 3]
});
// 하나라도 실패하면 실패
Promise.all([
Promise.resolve(1),
Promise.reject("에러"),
Promise.resolve(3)
])
.then(results => console.log(results))
.catch(error => console.log("실패:", error)); // "실패: 에러"
// Promise.race - 가장 먼저 완료된 것을 반환
Promise.race([
new Promise(resolve => setTimeout(() => resolve("느림"), 1000)),
new Promise(resolve => setTimeout(() => resolve("빠름"), 500))
])
.then(result => console.log(result)); // "빠름"
// Promise.allSettled - 모든 Promise 결과 반환 (실패해도)
Promise.allSettled([
Promise.resolve(1),
Promise.reject("에러"),
Promise.resolve(3)
])
.then(results => console.log(results));
// [
// { status: "fulfilled", value: 1 },
// { status: "rejected", reason: "에러" },
// { status: "fulfilled", value: 3 }
// ]Promise를 더 간결하고 읽기 쉽게 만들어줍니다.
// Promise 방식
function getUserPromise() {
return fetchUser(1)
.then(user => {
console.log(user);
return user;
})
.catch(error => {
console.error(error);
});
}
// async/await 방식 (훨씬 간결!)
async function getUserAsync() {
try {
const user = await fetchUser(1);
console.log(user);
return user;
} catch (error) {
console.error(error);
}
}
// async 함수는 항상 Promise를 반환
getUserAsync().then(user => {
console.log("완료:", user);
});async function fetchAllData() {
try {
// 순차적으로 실행 (하나씩)
const user = await fetchUser(1);
console.log("사용자:", user);
const posts = await fetchPosts(user.id);
console.log("게시글:", posts);
const comments = await fetchComments(posts[0].id);
console.log("댓글:", comments);
return { user, posts, comments };
} catch (error) {
console.error("에러 발생:", error);
throw error; // 에러 전파
}
}
fetchAllData();// ❌ 비효율적 (순차 실행 - 총 3초)
async function fetchSequential() {
const user1 = await fetchUser(1); // 1초
const user2 = await fetchUser(2); // 1초
const user3 = await fetchUser(3); // 1초
return [user1, user2, user3];
}
// ✅ 효율적 (병렬 실행 - 총 1초)
async function fetchParallel() {
const [user1, user2, user3] = await Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3)
]);
return [user1, user2, user3];
}
// 또는
async function fetchParallel2() {
const promise1 = fetchUser(1);
const promise2 = fetchUser(2);
const promise3 = fetchUser(3);
const user1 = await promise1;
const user2 = await promise2;
const user3 = await promise3;
return [user1, user2, user3];
}브라우저에서 HTTP 요청을 보내는 표준 방법입니다.
// 기본 GET 요청
async function getUsers() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
// 응답 상태 확인
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const users = await response.json(); // JSON 파싱
console.log(users);
return users;
} catch (error) {
console.error("데이터 조회 실패:", error);
}
}
getUsers();
// 특정 사용자 조회
async function getUser(id) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
if (!response.ok) {
throw new Error("사용자를 찾을 수 없습니다.");
}
const user = await response.json();
return user;
} catch (error) {
console.error("에러:", error.message);
}
}
getUser(1);async function createUser(userData) {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error("사용자 생성 실패");
}
const newUser = await response.json();
console.log("생성된 사용자:", newUser);
return newUser;
} catch (error) {
console.error("에러:", error);
}
}
// 사용 예제
createUser({
name: "김철수",
email: "kim@example.com",
age: 25
});async function updateUser(id, userData) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error("사용자 수정 실패");
}
const updatedUser = await response.json();
console.log("수정된 사용자:", updatedUser);
return updatedUser;
} catch (error) {
console.error("에러:", error);
}
}
// 사용 예제
updateUser(1, {
name: "박영희",
email: "park@example.com",
age: 30
});async function patchUser(id, partialData) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(partialData)
});
if (!response.ok) {
throw new Error("사용자 수정 실패");
}
const updatedUser = await response.json();
console.log("수정된 사용자:", updatedUser);
return updatedUser;
} catch (error) {
console.error("에러:", error);
}
}
// 사용 예제 (이름만 수정)
patchUser(1, { name: "이민수" });async function deleteUser(id) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: "DELETE"
});
if (!response.ok) {
throw new Error("사용자 삭제 실패");
}
console.log(`사용자 ${id} 삭제 완료`);
return true;
} catch (error) {
console.error("에러:", error);
return false;
}
}
// 사용 예제
deleteUser(1);JavaScript Object Notation - 데이터 교환 형식입니다.
// JavaScript 객체
const user = {
name: "김철수",
age: 25,
hobbies: ["독서", "영화감상"]
};
// 객체 → JSON 문자열 (JSON.stringify)
const jsonString = JSON.stringify(user);
console.log(jsonString);
// {"name":"김철수","age":25,"hobbies":["독서","영화감상"]}
// 예쁘게 포맷팅 (들여쓰기 2칸)
const prettyJson = JSON.stringify(user, null, 2);
console.log(prettyJson);
// {
// "name": "김철수",
// "age": 25,
// "hobbies": ["독서", "영화감상"]
// }
// JSON 문자열 → JavaScript 객체 (JSON.parse)
const parsed = JSON.parse(jsonString);
console.log(parsed.name); // "김철수"
// 에러 처리
try {
const invalid = JSON.parse("잘못된 JSON");
} catch (error) {
console.error("JSON 파싱 에러:", error.message);
}
// localStorage와 함께 사용
const data = { theme: "dark", fontSize: 16 };
localStorage.setItem("settings", JSON.stringify(data));
const loaded = JSON.parse(localStorage.getItem("settings"));
console.log(loaded); // { theme: "dark", fontSize: 16 }const API_BASE = "https://jsonplaceholder.typicode.com";
// API 헬퍼 함수
async function apiRequest(endpoint, options = {}) {
try {
const response = await fetch(`${API_BASE}${endpoint}`, {
headers: {
"Content-Type": "application/json",
...options.headers
},
...options
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// 204 No Content인 경우 (DELETE 등)
if (response.status === 204) {
return null;
}
return await response.json();
} catch (error) {
console.error("API 요청 실패:", error);
throw error;
}
}
// CRUD 함수들
const userAPI = {
// Create
create: async (userData) => {
return apiRequest("/users", {
method: "POST",
body: JSON.stringify(userData)
});
},
// Read (전체)
getAll: async () => {
return apiRequest("/users");
},
// Read (단일)
getById: async (id) => {
return apiRequest(`/users/${id}`);
},
// Update
update: async (id, userData) => {
return apiRequest(`/users/${id}`, {
method: "PUT",
body: JSON.stringify(userData)
});
},
// Patch
patch: async (id, partialData) => {
return apiRequest(`/users/${id}`, {
method: "PATCH",
body: JSON.stringify(partialData)
});
},
// Delete
delete: async (id) => {
return apiRequest(`/users/${id}`, {
method: "DELETE"
});
}
};
// 사용 예제
async function example() {
try {
// 전체 사용자 조회
const users = await userAPI.getAll();
console.log("전체 사용자:", users.length);
// 특정 사용자 조회
const user = await userAPI.getById(1);
console.log("사용자 1:", user.name);
// 새 사용자 생성
const newUser = await userAPI.create({
name: "김철수",
email: "kim@example.com"
});
console.log("생성된 사용자:", newUser);
// 사용자 수정
const updated = await userAPI.update(1, {
name: "박영희",
email: "park@example.com"
});
console.log("수정된 사용자:", updated);
// 사용자 삭제
await userAPI.delete(1);
console.log("삭제 완료");
} catch (error) {
console.error("작업 실패:", error);
}
}
example();async function fetchDashboardData() {
try {
// 병렬로 여러 API 호출
const [users, posts, comments] = await Promise.all([
fetch("https://jsonplaceholder.typicode.com/users").then(r => r.json()),
fetch("https://jsonplaceholder.typicode.com/posts").then(r => r.json()),
fetch("https://jsonplaceholder.typicode.com/comments").then(r => r.json())
]);
console.log(`사용자: ${users.length}명`);
console.log(`게시글: ${posts.length}개`);
console.log(`댓글: ${comments.length}개`);
return { users, posts, comments };
} catch (error) {
console.error("대시보드 데이터 로딩 실패:", error);
}
}
fetchDashboardData();async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.log(`시도 ${i + 1}/${maxRetries} 실패:`, error.message);
if (i === maxRetries - 1) {
throw new Error(`${maxRetries}번 시도 후 실패`);
}
// 지수 백오프 (exponential backoff)
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
}
}
}
// 사용 예제
fetchWithRetry("https://jsonplaceholder.typicode.com/users")
.then(data => console.log("성공:", data))
.catch(error => console.error("최종 실패:", error));async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === "AbortError") {
throw new Error("요청 시간 초과");
}
throw error;
}
}
// 사용 예제
fetchWithTimeout("https://jsonplaceholder.typicode.com/users", 3000)
.then(data => console.log("성공:", data))
.catch(error => console.error("에러:", error.message));async function safeFetch() {
try {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return { success: true, data };
} catch (error) {
return { success: false, error: error.message };
}
}
// 사용
const result = await safeFetch();
if (result.success) {
console.log("데이터:", result.data);
} else {
console.error("에러:", result.error);
}async function fetchWithLoading() {
let isLoading = true;
console.log("로딩 시작...");
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await response.json();
console.log("데이터 로드 완료");
return data;
} catch (error) {
console.error("에러 발생:", error);
} finally {
// 성공하든 실패하든 항상 실행
isLoading = false;
console.log("로딩 종료");
}
}
fetchWithLoading();exercises/03-async-practice.js 파일을 생성하고 다음을 구현해보세요:
// TODO: 1초 후에 랜덤 숫자(1~100)를 반환하는 Promise 함수 작성
// TODO: 숫자가 50 이상이면 resolve, 미만이면 reject
function getRandomNumber() {
// 여기에 코드 작성
}
// TODO: then-catch로 처리하기// TODO: JSONPlaceholder API에서 사용자 목록 가져오기
// URL: https://jsonplaceholder.typicode.com/users
async function fetchUsers() {
// 여기에 코드 작성
}
// TODO: 사용자 이름만 추출하여 배열로 반환
// TODO: try-catch로 에러 처리// TODO: 여러 사용자의 정보를 동시에 가져오기
// URL: https://jsonplaceholder.typicode.com/users/[id]
async function fetchMultipleUsers(ids) {
// Promise.all 사용
// 예: ids = [1, 2, 3]
}
// TODO: 모든 사용자의 이름을 배열로 반환// TODO: 게시글 API 함수 작성
// Base URL: https://jsonplaceholder.typicode.com/posts
const postAPI = {
// TODO: 모든 게시글 조회
getAll: async () => {
// 여기에 코드 작성
},
// TODO: 특정 게시글 조회
getById: async (id) => {
// 여기에 코드 작성
},
// TODO: 새 게시글 생성
create: async (postData) => {
// 여기에 코드 작성
},
// TODO: 게시글 삭제
delete: async (id) => {
// 여기에 코드 작성
}
};// TODO: 사용자 ID를 받아서 해당 사용자의 정보와 게시글을 함께 반환하는 함수
// 1. 사용자 정보 가져오기: /users/{id}
// 2. 사용자의 게시글 가져오기: /posts?userId={id}
// 3. 두 정보를 합쳐서 반환
async function getUserWithPosts(userId) {
// 여기에 코드 작성
// 반환 형식: { user: {...}, posts: [...] }
}