콜 스택(Call Stack) & 이벤트 루프(Event Loop) & 콜백 큐(Callback Queue)

콜 스택(Call Stack)
- 자바스크립트는 싱글 스레드 언어이기 때문에, 콜 스택이 하나만 존재한다.
- 콜 스택은 함수가 호출되면, 해당 함수를 콜 스택에 쌓아놓고 실행한다.
- 함수가 실행이 끝나면, 콜 스택에서 해당 함수를 제거한다.
function first() {
console.log('첫번째 함수');
second();
}
function second() {
console.log('두번째 함수');
}
first();
// 실행 순서
// 첫번째 함수
// 두번째 함수
이벤트 루프(Event Loop)
- 자바스크립트는 이벤트 중심 언어이기 때문에, 이벤트 루프가 존재한다.
- 이벤트 루프는 콜 스택과 콜백 큐를 감시하면서, 콜 스택이 비어있을 때 콜백 큐에 있는 함수를 콜 스택에 넣어 실행한다.
콜백 큐(Callback Queue)
- 콜백 큐는 비동기 함수의 콜백 함수를 담아두는 큐이다.
- 콜백 큐에 있는 함수는 콜 스택이 비어있을 때, 이벤트 루프에 의해 콜 스택에 넣어 실행된다.
console.log('시작');
setTimeout(() => {
console.log('타이머 완료');
}, 0);
console.log('끝');
// 출력: 시작 -> 끝 -> 타이머 완료
실제 동작 과정
- 동기 코드는 즉시 콜 스택에서 실행됩니다
- setTimeout 같은 비동기 작업은 Web API로 보내집니다
- Web API에서 작업이 완료되면 콜백은 콜백 큐로 이동합니다
- 이벤트 루프는 콜 스택이 비었는지 확인하고, 비었다면 콜백 큐의 첫 번째 작업을 콜 스택으로 가져옵니다
"자바스크립트는 마치 멀티태스킹을 하는 것처럼 여러 작업을 처리할 수 있습니다. 시간이 오래 걸리는 작업(예: 서버에서 데이터 가져오기)을 실행하더라도, 그동안 다른 작업들을 계속할 수 있습니다. 마치 라면을 끓이면서 그동안 핸드폰을 하는 것처럼..."
콜백 함수
콜백(Callback) 함수는 영문 그대로, 나중에 실행되는 함수를 뜻한다.
콜백 함수의 비동기 처리
- 자바스크립트는 이벤트 중심 언어이기 때문에
- 특정 이벤트가 발생하고, 그에 대한 결과가 나올 때까지 기다리지 않고 다음 코드를 실행한다.
// 먼저 호출했지만 3초 뒤에 실행되기 때문에 2초 뒤에 실행되는 함수보다 나중에 실행된다.
setTimeout(() => {
console.log('첫번째 실행');
}, 3000);
setTimeout(() => {
console.log('두번째 실행');
}, 2000);
// 실행 순서
// 두번째 실행
// 첫번째 실행
콜백 함수의 동기 처리
- 첫번째를 먼저 실행하고 두번째를 실행하고 싶으면
- '콜백 함수'를 이용해 비동기 작업을 동기적으로 처리해주어야 한다.
setTimeout(() => {
setTimeout(() => {
console.log('두번째 실행');
}, 2000);
console.log('첫번째 실행');
}, 3000);
// 실행 순서
// 첫번째 실행
// 두번째 실행
사용자 정의 함수의 동기 처리
- 아래 예제를 실행해보면,
첫번째 실행->두번째 실행->세번째 실행순으로 실행되고 - 동기적으로 처리된다.
function faker(callback) {
callback();
}
console.log('첫번째 실행');
faker(() => {
console.log('두번째 실행');
});
console.log('세번째 실행');
// 실행 순서
// 첫번째 실행
// 두번째 실행
// 세번째 실행
- 위 세 가지 실행부는 모두 동기적이기 때문에, 콜백 큐를 사용하지 않고 모두 콜 스택을 거쳐 실행된다.

API 비동기 처리
console.log('첫번째 실행');
setTimeout(() => {
console.log('세번째 실행');
}, 0);
console.log('두번째 실행');
// 실행 순서
// 첫번째 실행
// 두번째 실행
// 세번째 실행

console.log('첫번째 실행')이 콜 스택에 들어가서 실행됩니다.setTimeout이 콜 스택에 들어가면서- 콜백 함수는
Web APIs로 보내집니다 - 타이머가 완료되면 콜백 함수는
콜백 큐로 이동합니다
- 콜백 함수는
console.log('두번째 실행')이 콜 스택에 들어가서 실행됩니다.- 콜 스택이 비워지면, 이벤트 루프가 콜백 큐의
콜백 함수를 콜 스택으로 가져옵니다. console.log('세번째 실행')이 마지막으로 실행됩니다.
콜백 지옥(Callback Hell)
- 콜백 함수를 중첩해서 사용하다 보면, 코드가 복잡해지고 가독성이 떨어지는 현상을 말한다.
setTimeout(() => {
console.log('첫번째 실행');
setTimeout(() => {
console.log('두번째 실행');
setTimeout(() => {
console.log('세번째 실행');
}, 1000);
}, 1000);
}, 1000);

- 이러한 콜백 지옥을 해결하기 위해,
Promise나async/await을 사용한다.
Promise
Promise는 코드의 중첩이 발생하는 콜백 지옥을 해결하기 위한 객체이다.Promise는 단어 그대로 '약속'을 의미하며, 비동기 작업이 완료되었을 때, 성공했는지 실패했는지 알려주는 객체이다.
커피 주문 시나리오로 보는 Promise
1. 커피 주문
function order(sec, callback) {
setTimeout(() => {
callback(new Date().toISOString());
}, sec * 1000);
}
order(1, (time) => {
console.log(`커피 주문`, time);
});
order(2, (time) => {
console.log(`시럽 추가 주문`, time);
});
order(3, (time) => {
console.log(`휘핑 추가 주문`, time);
});
// 실행 순서
// 동시에 실행
- 위 코드는 동시에 실행되기 때문에, 순서가 보장되지 않는다.
- 실행 순서를 보장하기 위해 비동기 처리를 해야 한다.
function order(sec, callback) {
setTimeout(() => {
callback(new Date().toISOString());
}, sec * 1000);
}
order(1, (time) => {
console.log(`커피 주문`, time);
order(2, (time) => {
console.log(`시럽 추가 주문`, time);
order(3, (time) => {
console.log(`휘핑 추가 주문`, time);
});
});
});
// 실행 순서
// 커피 주문 -> 시럽 추가 주문 -> 휘핑 추가 주문
- 이런 콜백 지옥을 해결하기 위해
Promise를 사용한다.
1. 커피 주문 = Promise 생성
const orderCoffee = new Promise((resolve, reject) => {
// 바리스타가 커피 만드는 과정
});
- 커피를 주문하면 진동벨(Promise)을 줍니다
- 이 진동벨로 커피가 완성되었는지 알 수 있어요
2. Promise의 3가지 상태
- 대기중(Pending): "커피 제조중입니다" (진동벨 대기중)
- 성공(Fulfilled): "삐삐! 커피가 준비되었습니다" (진동벨 울림)
- 실패(Rejected): "죄송합니다. 머신 고장으로 제조가 불가능합니다" (진동벨 오류)
3. 실제 코드로 보는 커피 주문
const orderCoffee = new Promise((resolve, reject) => {
console.log("바리스타가 커피를 만들기 시작합니다!");
setTimeout(() => {
const isSuccess = true; // 커피가 잘 만들어졌다고 가정
if(isSuccess) {
resolve("주문하신 아메리카노 나왔습니다! ☕");
} else {
reject("죄송합니다. 머신 고장으로 제조가 불가능합니다 😢");
}
}, 3000); // 3초 동안 커피 제조중
});
// 커피 주문 결과 처리
orderCoffee
.then((result) => {
console.log(result); // "주문하신 아메리카노 나왔습니다! ☕"
})
.catch((error) => {
console.log(error); // "죄송합니다. 머신 고장으로 제조가 불가능합니다 😢"
});
4. Promise의 장점
- 콜백: "커피 주문하고, 시럽 추가하고, 휘핑 추가하고..." (복잡)
- Promise: 진동벨 하나로 모든 과정을 깔끔하게 처리 (.then 체이닝)
5. Promise 체이닝
Promise는.then을 이용해 여러 개의 비동기 작업을 순차적으로 처리할 수 있다.
// 콜백 지옥 버전
orderCoffee(function(coffee) {
addSyrup(coffee, function(withSyrup) {
addWhippedCream(withSyrup, function(complete) {
console.log("주문 완료!");
});
});
});
// Promise 버전 (깔끔!)
orderCoffee()
.then(coffee => addSyrup(coffee))
.then(withSyrup => addWhippedCream(withSyrup))
.then(() => console.log("주문 완료!"))
.catch(error => console.log("주문 실패:", error));
async/await
async/await는Promise를 더 쉽게 사용할 수 있도록 ES8에서 도입된 문법이다.
무인 카페 주문 시나리오로 보는 async/await
1. 기본 개념
// 기존 Promise 방식 (진동벨)
orderCoffee()
.then(coffee => console.log("커피 완성!"))
.catch(error => console.log("주문 실패"));
// async/await 방식 (셀프 주문기)
async function orderCoffee() {
try {
const coffee = await makeCoffee(); // 커피가 완성될 때까지 기다림
console.log("커피 완성!");
} catch(error) {
console.log("주문 실패");
}
}
2. Promise vs async/await 주문 비교
// 진동벨(Promise) 방식
const orderByBell = () => {
getCoffee()
.then(coffee => addSyrup(coffee))
.then(withSyrup => addWhippedCream(withSyrup))
.then(() => console.log("주문 완성!"))
.catch(error => console.log("주문 실패"));
}
// 셀프 주문기(async/await) 방식
const orderBySelf = async () => {
try {
const coffee = await getCoffee(); // 1. 커피 추출 기다리기
const withSyrup = await addSyrup(coffee); // 2. 시럽 추가 기다리기
const completed = await addWhippedCream(withSyrup); // 3. 휘핑 추가 기다리기
console.log("주문 완성!");
} catch(error) {
console.log("주문 실패");
}
}
// 실행
orderByBell(); // 진동벨 주문
orderBySelf(); // 셀프 주문기 주문
3. async/await 장점
쉽고 직관적인 코드 작성이 가능하다.
async: "셀프 주문기를 사용하겠습니다"await: "이 작업이 끝날 때까지 기다립니다"try-catch: "주문 실패시 환불해드립니다"
'Front > Node.js' 카테고리의 다른 글
| 프론트엔드 개발자를 위한 데이터베이스 초보자 가이드 (1) | 2024.12.26 |
|---|---|
| Node.js 모듈과 객체 (0) | 2024.12.25 |
| next.js로 CRUD API 서버 만들기 (1) | 2024.12.09 |
| node.js로 API 통신 구현하기 (0) | 2024.12.09 |
| Express 모듈을 사용하여 서버 만들기 (1) | 2024.12.09 |