자바스크립트는 싱글 스레드 기반 언어이다.
즉, 콜 스택(Call Stack)이 하나뿐이며, 동시에 여러 작업을 처리할 수 없다.
💡 싱글 스레드(Single-Thread)란 ?
스레드(Thread)는 프로세스의 실행 단위로, 싱글 스레드라는 건 이 스레드가 하나밖에 없다는 것을 의미한다.
따라서 한 번에 하나의 작업만 처리할 수 있고, 다른 작업들은 앞선 작업이 끝날 때까지 기다려야 한다.
즉, 동시에 여러 작업을 병렬로 처리하지 못한다.
하지만 자바스크립트 기반의 우리가 사용하는 웹 브라우저에서는 여러 작업을 동시에 처리할 수 있다.
예를 들어 음악을 들으면서 우리는 파일을 다운로드 할 수 있고, 동시에 웹 페이지의 스크롤도 내릴 수 있다.
어떻게 자바스크립트는 싱글 스레드 언어이면서 동시에 여러 작업들을 수행할 수 있을까??

자바스크립트의 비동기 처리
자바스크립트가 싱글 스레드 언어임에도 동시에 여러 작업을 처리할 수 있는 실행 환경 때문이다
브라우저에서 제공하는 Web API나 Node.js의 libuv같은 런타임 라이브러리를 이용해서 비동기 작업을 처리할 수 있다.
다시 말하자면, 자바스크립트 언어 자체는 싱글 스레드이지만, 자바스크립트가 실행되는 환경이 이벤트 루프(Event Loop)와 비동기 API를 지원하기 때문에 여러 작업이 동시에 실행되는 것처럼 보이는 것이다.
이 말을 이해하기 위해서는, 자바스크립트 엔진의 기본 구조와 브라우저의 내부 구성을 파악해야 한다.
자바스크립트 엔진의 구조

- 메모리 힙
- 컴퓨터가 정보를 저장하는 공간
- 배열, 함수, 객체 등의 데이터가 저장되는 동적 메모리 영역
- 콜 스택
- 자바스크립트 엔진이 코드 실행을 위해 사용하는 메모리 구조
- 함수를 호출하게 되면 해당 함수의 실행 컨택스트가 콜 스택에 쌓임
- LIFO(Last In First Out) 방식으로 작동
여기서 자바스크립트의 비동기 동작 원리를 이해하기 위해서는 콜 스택이 중요하다 !
💡 콜 스택(Call Stack)
모든 자바스크립트 코드는 콜 스택에서 실행된다.
함수가 호출되면 콜 스택에 쌓이고, 실행이 끝나면 스택에서 제거된다.
이 과정은 동기적으로 차례대로 처리된다.
이걸 기억한 채로 다음으로 넘어가보자 !
브라우저의 내부 구성
Web APIs
브라우저가 제공하는 비동기 기능 실행 영역
`setTimeout`, `fetch` DOM 이벤트 같은 비동기 작업은 js 엔진이 직접 실행하지 않고, 대신 브라우저가 제공하는 Web API 영역에서 처리된다.
- `setTimeout` : 브라우저 타이머가 실행
- `fetch` : 네트워크 스레드에서 요청
- `addEventListener` : 이벤트 발생 대기
콜백 큐(Callback Queue)
비동기 작업이 완료된 후 실행 대기 중인 콜백들이 저장되는 공간
비동기 작업이 완료되면, 그 결과(콜백 함수)가 바로 실행되지 않고 큐(Queue)에 들어간다.
큐의 종류
- 태스크 큐(Task Queue/Macrotask Queue): `setTimeout`, `setInterval`, 이벤트 핸들러 등
- 마이크로태스크 큐(Microtask Queue): `Promise.then`, `async/await` 등
- 애니메이션 프레임 큐 : `requestAnimationFrame`
이벤트 루프(Event Loop)
콜 스택과 큐를 감시하며, 스택이 비면 큐에서 콜백을 꺼내 실행하는 스케줄러
이벤트 루프는 자바스크립트 실행 환경의 스케쥴러 같은 존재이다.
계속해서 콜 스택이 비어있는지를 감시하면서, 비어 있다면 대기열(큐)에 있는 작업을 하나씩 스택으로 옮겨준다.
- 먼저 콜 스택을 확인한다. (현재 실행 중인 작업이 있는지?)
- 스택이 비어 있다면, 우선순위가 높은 마이크로태스크 큐(Promise 같은 비동기 작업)부터 모든 작업을 처리 한다.
- 마이크로태스크 큐가 비어 있으면 태스크 큐(setTimeout, 이벤트 핸들러 등)에서 하나의 작업을 가져온다.
- 브라우저 렌더링이 필요한 경우 렌더링을 수행한다.
- 필요 시 애니메이션 프레임 큐 작업이 실행된다.
실행 원리 예시
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
- `console.log('1')` : 콜 스택에서 즉시 실행 -> `1` 출력
- `setTimeout()` : Web API로 전달, 0ms 후 태스크 큐에 콜백 추가
- `Promise.resolve().then()` : 마이크로태스크 큐에 콜백 추가
- `console.log('4')` : 콜 스택에서 즉시 실행 -> `4` 출력
- 콜 스택이 비었으니 이벤트 루프가 마이크로태스크 큐를 확인 -> `3` 출력
- 마이크로태스크 큐가 비었으니 태스크 큐 확인 -> `2` 출력
결과
1 -> 4 -> 3 -> 2
정리

비동기 작업이 시작되면, 자바스크립트 엔진은 해당 작업을 브라우저의 Web API 영역으로 전달하고 즉시 다음 코드를 실행한다.
Web API에서 작업이 완료되면 그 결과는 콜백 큐로 이동해 순서를 기다리게 된다.
이벤트 루프는 계속 콜 스택에 상태를 감시하다가, 스택이 비어있을 때 큐에서 대기 중인 작업을 가져와 실행시킨다.
이런 매커니즘 덕분에 자바스크립트는 하나의 스레드로도 마치 여러 작업을 동시에 처리하는 것처럼 보이는 것이다 !