JavaScript로 개발하다 보면 Promise는 피할 수 없는 존재다. 하지만 처음 접하면 "이게 뭔데?" 싶은 게 사실이다.
오늘은 Promise의 기본 개념부터 실전 활용까지 정리해봤다.
동기 vs 비동기, 카페 예시로 이해하기

동기 처리를 카페에 비유하면 이렇다. 첫 번째 손님이 주문하고 커피 받을 때까지 뒤의 모든 손님이 대기한다. 비효율적이다.

비동기 처리는 다르다. 주문받고 진동벨 주고 다음 손님 받는다. 커피 완성되면 진동벨로 알려준다. 훨씬 효율적이다.
웹 개발도 마찬가지다. API 호출 결과를 기다리느라 화면이 멈추면 사용자 경험이 최악이 된다.
Promise가 뭔가?
Promise는 JavaScript에서 비동기 작업을 다루는 객체다. 작업 상태에 따라 3가지 상태를 가진다.

- Pending (대기): 아직 결과 안 나옴
- Fulfilled (이행): 작업 성공
- Rejected (거부): 작업 실패
Promise의 생명주기를 보면 이렇다:
- Promise 생성 → Pending 상태로 시작
- 작업 성공 → Fulfilled 상태, .then() 실행
- 작업 실패 → Rejected 상태, .catch() 실행
- 체이닝 → 다음 Promise로 연결 가능
기본 사용법
// Promise 생성
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
let success = true;
if (success) {
resolve("작업 성공!");
} else {
reject("작업 실패...");
}
}, 2000);
});
// Promise 사용
promise
.then((message) => {
console.log(message); // 성공 시 실행
})
.catch((error) => {
console.log(error); // 실패 시 실행
});
처음엔 이 문법이 어색하게 느껴졌는데, 익숙해지면 콜백 지옥보다 훨씬 깔끔하다.
Async/Await로 더 깔끔하게
ES8부터 추가된 async/await는 Promise를 동기 코드처럼 작성할 수 있게 해준다. 가독성이 확실히 좋아진다.
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
- async 함수: 항상 Promise 반환
- await: Promise 완료까지 대기
- try/catch: 에러 처리
실무에서는 대부분 async/await 방식을 쓴다. Promise 체인보다 직관적이기 때문이다.
Promise 활용
Promise Chaining
여러 비동기 작업을 순차적으로 처리할 때 사용한다.
fetchData('/api/data')
.then(response => processResponse(response))
.then(processedData => displayData(processedData))
.catch(error => handleError(error));
Async/Await로 변환하면
async function getData() {
try {
const response = await fetchData('/api/data');
const processedData = await processResponse(response);
displayData(processedData);
} catch (error) {
handleError(error);
}
}
훨씬 읽기 쉽다.
Promise 정적 메서드
Promise.all vs Promise.allSettled
- Promise.all: 모든 Promise가 성공해야 성공
const requests = [
fetch('/api/data1'),
fetch('/api/data2'),
fetch('/api/data3')
];
Promise.all(requests)
.then(responses => Promise.all(responses.map(res => res.json())))
.then(([data1, data2, data3]) => {
// 모든 데이터 성공적으로 받았을 때
renderAllData(data1, data2, data3);
})
.catch(error => {
// 하나라도 실패하면 여기로
console.error('API 호출 실패:', error);
});
문제점: 하나만 실패해도 전체 실패 처리된다.
- Promise.allSettled: 성공/실패 상관없이 모든 결과 반환
Promise.allSettled(requests)
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('성공:', result.value);
} else {
console.error('실패:', result.reason);
}
});
});
실무에서는 allSettled를 더 많이 쓴다. 일부 실패해도 나머지는 정상 처리할 수 있기 때문이다.
Promise.race vs Promise.any
- Promise.race: 가장 빨리 완료된 결과 반환 (성공/실패 무관)
Promise.race([
fetch('/server1/data'),
fetch('/server2/data')
])
.then(result => {
console.log('가장 빠른 응답:', result);
})
.catch(error => {
console.error('가장 빠른 실패:', error);
});
- Promise.any: 가장 빨리 성공한 결과 반환
Promise.any([
fetch('/server1/data'),
fetch('/server2/data')
])
.then(result => {
console.log('첫 번째 성공:', result);
})
.catch(error => {
// 모든 요청이 실패했을 때만 실행
console.error('모든 요청 실패:', error);
});
실무 활용: CDN 서버 여러 개 중 가장 빠른 곳에서 데이터 받을 때 유용하다.
정리
- Promise는 비동기 작업의 상태를 관리하는 객체
- async/await가 가독성 면에서 더 우수
- Promise.all: 모든 작업 성공 필요
- Promise.allSettled: 부분 실패 허용
- Promise.race: 속도 우선
- Promise.any: 성공 우선
예전엔 콜백 함수로 비동기를 처리했는데, Promise 도입 후 코드가 훨씬 깔끔해졌다. 특히 async/await 문법은 비동기 코드를 동기처럼 읽을 수 있게 해서 유지보수성이 향상됐다.
실무에서는 API 호출, 파일 업로드, 이미지 로딩 등 거의 모든 비동기 작업에서 Promise를 사용한다. 그러므로 이번에 제대로 알아두자.
'Frontend > Javascript' 카테고리의 다른 글
| JavaScript - JavaScript의 복사, 비교, 그리고 Immer (0) | 2025.11.05 |
|---|---|
| JavaScript - 프로토타입과 상속 (0) | 2025.09.29 |
| Javascript - 이벤트 루프 (0) | 2025.09.26 |