이 글은 『자바스크립트 딥 다이브』를 공부하며 정리한 내용입니다.
자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백 함수(callback function) 를 사용한다.
하지만 전통적인 콜백 패턴은 다음과 같은 한계가 있다.
이러한 문제를 해결하기 위해 ES6에서는 프로미스(Promise) 가 도입되었다.
앞서 자바스크립트 실행 컨텍스트와 이벤트 루프 정리 에서 살펴봤듯,
비동기 함수는 태스크 큐(Task Queue) 에 콜백이 대기하고 있다가
콜 스택(Call Stack) 이 비었을 때 이벤트 루프(Event Loop) 에 의해 실행된다.
즉, 비동기 함수는 호출 시점에 바로 실행되지 않으며,
그 결과를 외부로 반환하거나 상위 스코프 변수에 즉시 할당할 수 없다.
const get = url => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
return JSON.parse(xhr.response);
} else {
console.error('요청 실패', xhr.status);
}
};
}
const res = get("/posts/1");
console.log(res); // undefined
get 함수가 호출되면 XMLHttpRequest 객체가 생성되고,
HTTP 요청이 전송된 뒤 xhr.onload 이벤트 핸들러가 바인딩된다.
하지만 get 함수 자체에는 명시적인 반환문이 없기 때문에 undefined 를 반환한다.
이때 xhr.onload 내부의 return 문은 get 함수의 반환값이 아니다.
get은 이미 종료되어 스택에서 제거되었기 때문이다.
let value;
const get = url => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
value = JSON.parse(xhr.response);
} else {
console.error('요청 실패', xhr.status);
}
};
}
get("/posts/1");
console.log(value); // undefined
이 경우에도 기대한 대로 동작하지 않는다.
xhr.onload는 비동기적으로 실행되므로,
console.log(value)가 실행될 때는 서버 응답이 아직 도착하지 않은 상태다.
즉, xhr.onload는 항상 console.log(value) 실행이 끝난 이후에 동작한다.
비동기 함수는 결과를 즉시 반환할 수 없기 때문에,
결과 처리를 콜백 내부에서 수행해야 한다.
그러나 비동기 처리가 중첩되면 다음과 같이 코드의 복잡도가 급격히 증가한다.
getUser(userId, user => {
getPosts(user.id, posts => {
getComments(posts[0].id, comments => {
console.log(comments);
});
});
});
이런 구조를 콜백 헬(Callback Hell) 이라고 한다.
다음 게시물에서 Promise와 fetch API에 대해서 알아보자.