자바스크립트(JavaScript)에서 비동기 처리(asynchronous programming)는 사용자 경험(UX)과 시스템 안정성에 큰 영향을 미치는 핵심 개념입니다. 특히 네트워크 요청, 타이머, 파일 처리처럼 시간이 걸리는 작업을 효과적으로 다루기 위해, JS는 오랜 시간 동안 콜백(callback), Promise, async/await 등의 방식을 발전시켜 왔습니다. 이 중에서도 Promise는 콜백 헬을 해결하고 코드의 흐름을 구조화하는 데 중심적인 역할을 하며, 이후 async/await로 자연스럽게 이어졌습니다. 본 글에서는 Promise의 개념과 동작 방식, 콜백/async와의 비교, 실무 활용법과 면접 대응 전략까지 학습과 실전에 도움이 되도록 정리합니다.
Promise란 무엇이며 왜 필요한가?
Promise는 자바스크립트에서 비동기 작업의 완료 또는 실패를 표현하는 객체입니다. 쉽게 말해, "미래에 어떤 일이 완료될 것이라는 약속"을 의미하며, 그 결과값을 나중에 사용할 수 있도록 합니다.
1. 콜백 헬의 문제
Promise가 등장하기 전에는 콜백 기반 비동기 처리가 일반적이었습니다. 하지만 콜백을 중첩해 작성할 경우 다음과 같은 콜백 헬(callback hell) 문제가 발생합니다.
getData(function(result1) {
processData(result1, function(result2) {
saveData(result2, function(result3) {
console.log('완료');
});
});
});
이처럼 깊게 중첩되면 코드 가독성과 유지보수가 떨어집니다. Promise는 이러한 구조를 평평하게(flatten) 만들고, 비동기 흐름을 체계적으로 처리할 수 있게 해줍니다.
2. Promise의 상태
- pending: 아직 작업이 끝나지 않은 상태
- fulfilled: 작업이 성공적으로 완료된 상태
- rejected: 작업이 실패한 상태
Promise는 위 세 가지 상태 중 하나를 가집니다. 상태는 한 번 정해지면 바뀌지 않습니다(불변성). 이 특성 덕분에 예측 가능한 비동기 흐름 제어가 가능합니다.
3. 기본 문법
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('성공');
} else {
reject('실패');
}
}, 1000);
});
promise
.then(result => console.log(result)) // 성공 시
.catch(error => console.error(error)) // 실패 시
.finally(() => console.log('작업 종료'));
.then()은 작업 성공 시 실행되고, .catch()는 실패 시, .finally()는 무조건 실행되는 후처리 구문입니다.
4. Promise 체이닝
여러 비동기 작업을 순차적으로 처리할 때는 다음과 같이 체이닝(Chaining) 방식으로 작성할 수 있습니다.
fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => displayPosts(posts))
.catch(error => console.error('에러 발생:', error));
각 then은 이전 작업의 결과를 다음 작업에 넘기며, 코드 흐름이 직관적이고 간결해집니다.
콜백과 async/await과의 비교
Promise는 자바스크립트 비동기 처리의 진화 과정에서 중간 단계입니다. 이 흐름은 아래와 같이 이해할 수 있습니다:
1. 콜백 방식
콜백은 함수의 인자로 다른 함수를 전달하여 비동기 완료 후 호출하는 방식입니다.
function getData(callback) {
setTimeout(() => {
callback('데이터');
}, 1000);
}
단점: 중첩이 깊어지면 코드 유지보수 어려움 (callback hell)
2. Promise 방식
콜백을 분리된 then 체인으로 표현 가능해지고, 에러 처리도 체계화됩니다.
getDataPromise()
.then(data => process(data))
.catch(err => console.error(err));
3. async/await 방식
Promise 위에 구성된 문법으로, 동기 코드처럼 보이지만 실제로는 비동기 흐름입니다.
async function main() {
try {
const data = await getData();
const result = await process(data);
console.log(result);
} catch (err) {
console.error(err);
}
}
비교 요약표
방식 | 가독성 | 에러 처리 | 중첩 구조 | 사용 시기 |
---|---|---|---|---|
콜백 | 낮음 | 분산 처리 | 심함 | 구식 코드, 이벤트 중심 |
Promise | 높음 | .catch로 일괄 처리 | 적음 | ES6 이후 일반적 사용 |
async/await | 최고 | try/catch로 구조화 | 없음 | 현대적 코드, 대부분 권장 |
요약: 콜백은 구식, Promise는 중간 단계, async/await은 가독성과 유지보수 측면에서 가장 우수합니다.
실무 활용법과 면접 질문 대응 전략
1. 실무 예시: API 호출
async function fetchUserProfile(userId) {
try {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('API 호출 실패');
const data = await res.json();
return data;
} catch (error) {
console.error('에러 발생:', error);
throw error;
}
}
실제 업무에서는 Promise 체이닝보다는 async/await + try/catch 조합이 표준입니다. 특히 await가 직렬적으로 작동</strong하기 때문에, 동시에 여러 요청을 보낼 때는 Promise.all
을 활용합니다.
2. 고급 활용: Promise.all
const [user, posts] = await Promise.all([
fetchUser(),
fetchPosts()
]);
위 코드는 두 작업을 병렬로 실행한 후 결과를 동시에 받습니다. 네트워크 효율성과 속도 개선에 유리합니다.
3. 면접 질문 예시
- “Promise란 무엇인가요?”
- “콜백 함수와 Promise의 차이는?”
- “async/await이 어떻게 동작하나요?”
- “Promise.all()과 Promise.race()의 차이는?”
4. 면접 답변 전략
“Promise는 비동기 결과를 다루는 객체이며, 상태를 관리하고 체이닝이 가능합니다. 콜백보다 가독성이 좋고, async/await과 함께 사용되면 동기 코드처럼 작성할 수 있어 유지보수성이 뛰어납니다. 실무에선 API 호출, 에러 처리, 병렬 실행 등 다양한 케이스에 사용하고 있습니다.”
팁: 실제 사용 경험을 곁들이면 훨씬 신뢰도 높은 답변이 됩니다.
결론
자바스크립트의 비동기 처리 핵심은 Promise입니다. 콜백 기반 코드의 복잡성을 줄이고, then/catch 체이닝으로 구조화된 흐름을 가능하게 만들었으며, async/await의 기반이기도 합니다.
실무에서는 API 호출, 데이터 처리, 파일 업로드 등 다양한 비동기 상황에서 Promise와 async/await이 필수처럼 사용되고 있으며, 면접에서도 그 개념, 비교, 활용 경험에 대해 자주 묻는 항목입니다. 따라서 Promise의 본질과 async/await의 관계를 깊이 이해하고, 실제 코드로 설명할 수 있도록 연습하는 것이 중요합니다.