Promise의 세 가지 상태와 then/catch/finally 체이닝을 이해한다. Promise.all, Promise.race, Promise.allSettled 등 정적 메서드와 fetch API를 활용한 실전 패턴을 익힌다.
Promise — 콜백 헬의 해결책
Promise는 비동기 작업의 최종 완료(또는 실패)와 그 결과값을 나타내는 객체입니다. ES6에서 도입되었으며, 콜백 헬을 체이닝 방식으로 해결합니다.
Promise의 세 가지 상태
Pending(대기) → 비동기 작업 진행 중
Fulfilled(이행) → 작업 성공, resolve() 호출됨
Rejected(거부) → 작업 실패, reject() 호출됨
// 상태는 한 번만 변경됨 (Pending → Fulfilled 또는 Rejected)
Promise 생성
// 기본 구조
const promise = new Promise((resolve, reject) => {
// 비동기 작업 수행
setTimeout(() => {
const success = true;
if (success) {
resolve("작업 완료!"); // 성공 시
} else {
reject(new Error("작업 실패!")); // 실패 시
}
}, 1000);
});
// 즉시 이행/거부
const resolved = Promise.resolve(42); // 즉시 fulfilled
const rejected = Promise.reject(new Error("에러")); // 즉시 rejected
then / catch / finally
fetchUser(1)
.then(user => {
console.log("유저:", user);
return fetchOrders(user.id); // 다음 Promise 반환 → 체이닝
})
.then(orders => {
console.log("주문:", orders);
return orders[0]; // 일반 값 반환도 가능
})
.then(firstOrder => {
console.log("첫 주문:", firstOrder);
})
.catch(error => {
// 체인 어디서든 에러가 나면 여기서 처리
console.error("에러 발생:", error.message);
})
.finally(() => {
// 성공/실패 관계없이 항상 실행
setLoading(false);
hideSpinner();
});
// 콜백 헬 vs Promise 체이닝 비교
// 콜백: 가로로 길어짐 (들여쓰기 증가)
// Promise: 세로로 읽기 좋음 (체이닝)
fetch API — 가장 많이 쓰는 Promise
// GET 요청
fetch("https://api.example.com/users/1")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP 에러: ${response.status}`);
}
return response.json(); // JSON 파싱도 비동기!
})
.then(user => {
console.log(user);
})
.catch(error => {
console.error("요청 실패:", error);
});
// POST 요청
fetch("https://api.example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Kim", age: 30 })
})
.then(res => res.json())
.then(data => console.log("생성됨:", data))
.catch(err => console.error(err));
Promise 정적 메서드
// Promise.all — 모두 완료될 때까지 대기 (하나라도 실패하면 즉시 reject)
Promise.all([
fetchUser(1),
fetchProducts(),
fetchCategories()
]).then(([user, products, categories]) => {
// 셋 다 완료됐을 때만 실행
renderPage(user, products, categories);
}).catch(err => {
// 하나라도 실패하면 catch
console.error(err);
});
// Promise.allSettled — 모두 완료 대기, 결과 상태 포함 (ES2020)
Promise.allSettled([
fetchUser(1),
fetchProducts(),
fetchSomethingThatFails()
]).then(results => {
results.forEach(result => {
if (result.status === "fulfilled") {
console.log("성공:", result.value);
} else {
console.log("실패:", result.reason);
}
});
});
// Promise.race — 가장 먼저 완료된 것만 반환
Promise.race([
fetch("https://api1.example.com/data"),
fetch("https://api2.example.com/data"), // 백업 서버
]).then(response => response.json())
.then(data => console.log("가장 빠른 응답:", data));
// Promise.any — 가장 먼저 성공한 것 반환 (모두 실패해야 reject)
Promise.any([
tryServer1(),
tryServer2(),
tryServer3(),
]).then(result => console.log("첫 번째 성공:", result))
.catch(() => console.error("모두 실패"));
Promise 에러 처리 패턴
// 특정 단계에서만 에러 처리 후 계속 진행
fetchUser(1)
.then(user => fetchOrders(user.id))
.catch(err => {
console.warn("주문 로드 실패, 빈 배열 사용:", err);
return []; // 에러를 복구하고 체인 계속
})
.then(orders => renderOrders(orders)); // 빈 배열이라도 실행됨
// 에러 타입 구분
fetchData()
.catch(error => {
if (error.name === "NetworkError") {
return retryRequest();
}
if (error.status === 401) {
redirectToLogin();
}
throw error; // 처리 못 하면 다음 catch로 전달
})
Promise 직접 만들기 — 유틸리티 패턴
// setTimeout을 Promise로 래핑
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 사용
delay(1000).then(() => console.log("1초 후"));
// 재시도 로직
function fetchWithRetry(url, maxRetries = 3) {
return fetch(url).catch(err => {
if (maxRetries === 0) throw err;
return delay(1000).then(() => fetchWithRetry(url, maxRetries - 1));
});
}
관련 검색어 · Promise 사용법, then catch finally, fetch API, Promise.all allSettled, Promise 체이닝, 자바스크립트 비동기 처리