콜 스택(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 |