콜 스택과 Web API: 오래 걸리는 일은 누가 대신 해주는가
콜 스택은 그냥 함수 호출 스택이다
용어가 거창해 보이지만 콜 스택은 여러분이 이미 알고 있는 개념입니다. 함수를 호출하면 스택에 프레임이 쌓이고, 함수가 끝나면 그 프레임이 빠집니다. 후입선출(LIFO), 그게 전부입니다.
function third() {
console.log(new Error().stack); // 현재 스택을 그대로 찍어본다
}
function second() { third(); }
function first() { second(); }
first();
// third 안에서 찍힌 스택: third -> second -> first -> (global)이 구조를 직접 눈으로 보고 싶다면 재귀 함수에 종료 조건을 빼먹어 보세요.
function recurse() {
return recurse();
}
recurse(); // Uncaught RangeError: Maximum call stack size exceeded스택 프레임이 무한히 쌓이다가 엔진이 정해둔 한계(브라우저마다 다르지만 대략 만 개 안팎)를 넘으면 이 에러가 납니다. "스택 오버플로"라는 이름 그대로, 콜 스택이라는 자료구조가 실제로 넘친 겁니다. 이걸 한 번쯤 직접 발생시켜 보면 콜 스택이 추상적인 개념이 아니라 진짜 메모리 구조라는 게 체감됩니다.
Web API 는 자바스크립트가 아니다
setTimeout, fetch, addEventListener, document.querySelector — 이것들은 사실 자바스크립트 언어 명세(ECMAScript)에 없습니다. 전부 브라우저가 제공하는 Web API 입니다. Node.js 로 가면 setTimeout 은 있지만 document 는 없죠. 왜냐하면 Node.js 는 DOM 이 없는 서버 런타임이고, 대신 fs, http 같은 자기만의 API 를 제공하기 때문입니다.
이 구분이 중요한 이유는, "비동기"라고 뭉뚱그려 부르는 기능들의 실제 구현체가 자바스크립트 엔진(V8, SpiderMonkey 등) 바깥에 있다는 걸 알아야 다음 챕터의 큐 동작이 이해되기 때문입니다. setTimeout(fn, 1000) 을 호출하면 벌어지는 일은 이렇습니다.
- 자바스크립트 엔진이
setTimeout호출을 콜 스택에 올린다 - 브라우저(Web API 쪽)에게 "1000ms 뒤에
fn을 실행 큐에 넣어줘"라고 등록만 하고 콜 스택에서 즉시 내려온다 - 브라우저는 별도로 타이머를 돌리다가 1000ms 가 지나면
fn을 태스크 큐에 밀어 넣는다 - 콜 스택이 완전히 비어 있을 때, 이벤트 루프가 태스크 큐에서
fn을 꺼내 콜 스택에 올린다
setTimeout 자체는 콜 스택에 아주 잠깐 머물다가 곧바로 내려옵니다. 실제로 대기하는 건 브라우저 쪽 타이머지 자바스크립트 코드가 아닙니다. 이 흐름을 그림으로 보면 훨씬 명확합니다.
sequenceDiagram participant Stack as 콜 스택 participant WebAPI as Web API (브라우저) participant Queue as 태스크 큐 participant Loop as 이벤트 루프 Stack->>WebAPI: setTimeout(fn, 1000) 등록 WebAPI-->>Stack: 즉시 반환 (콜 스택은 계속 진행) Note over WebAPI: 1000ms 대기는 브라우저가 처리 WebAPI->>Queue: 1000ms 후 fn 을 큐에 삽입 Loop->>Stack: 콜 스택이 비었는지 계속 확인 Loop->>Queue: 비어 있으면 큐에서 fn 을 꺼낸다 Queue->>Stack: fn 실행
여기서 이벤트 루프가 하는 일은 딱 하나입니다. "콜 스택이 비었는가?"를 끊임없이 확인하고, 비어 있으면 큐에서 다음 작업을 꺼내 콜 스택에 올려주는 것. 이 단순한 폴링 루프 하나가 자바스크립트 비동기 처리의 전부입니다. 신비로울 것도 복잡할 것도 없습니다.
fetch 는 왜 다르게 느껴지는가
fetch 도 원리는 같지만 한 가지가 다릅니다. setTimeout 의 콜백은 태스크 큐로 가는데, fetch 가 반환하는 Promise 의 콜백(.then)은 마이크로태스크 큐라는 별도의 줄로 갑니다. 두 큐가 왜 나뉘어 있고 어느 쪽이 먼저 처리되는지는 4장에서 다룹니다. 지금 단계에서는 "Web API 가 작업을 대신 처리하고, 끝나면 콜백을 어딘가의 큐에 넣어준다"는 그림만 확실히 잡고 넘어가면 충분합니다.