자바스크립트는 기본적으로 싱글 스레드(Single-threaded) 언어이다. 이는 한 번에 하나의 작업만 처리할 수 있음을 의미한다. 그러나 자바스크립트는 네트워크 요청(예: 데이터 가져오기), 타이머(예: setTimeout
), 사용자 이벤트(예: 클릭)와 같이 시간이 오래 걸리는 비동기 작업들을 메인 스레드를 멈추지 않고 처리한다. 이 모든 것은 비동기 실행 환경(Asynchronous Execution Environment) 또는 동시성 모델(Concurrency Model) 덕분이다.
이 모델은 몇 가지 핵심 구성 요소들로 이루어져 있다.
자바스크립트 엔진 (JavaScript Engine)
자바스크립트 코드를 파싱하고, 컴파일하고, 실행하는 역할을 담당한다. V8 엔진(Chrome, Node.js), SpiderMonkey(Firefox), JavaScriptCore(Safari) 등이 있다.
런타임 환경 (Runtime Environment)
자바스크립트 엔진만으로는 부족한, 자바스크립트 코드가 외부 세상과 상호작용할 수 있도록 해주는 추가적인 구성 요소들을 포함한다. 주요 런타임 환경은 브라우저와 Node.js다.
웹 API (Web APIs) / Node.js API (Node.js APIs):
setTimeout()
, fetch()
, XMLHttpRequest()
, DOM 조작 (document.getElementById()
, addEventListener()
), localStorage
등 브라우저가 자바스크립트에게 제공하는 기능들이다. 이들은 브라우저에 내장되어 있으며, 자바스크립트 엔진과는 별개로 존재한다.fs
모듈(파일 시스템 접근), http
모듈(네트워크 통신), setTimeout()
등 Node.js 런타임이 제공하는 기능들이다. 이들 역시 V8 엔진 외부에 존재한다.태스크 큐 (Task Queues):
setTimeout
, setInterval
, DOM 이벤트 핸들러, fetch
콜백 등이 비동기 작업 완료 후 대기하는 곳이다. 이 큐는 런타임 환경에서 관리된다..then()
, .catch()
, .finally()
콜백, queueMicrotask()
콜백 등이 대기하는 곳으로, 매크로태스크 큐보다 높은 우선순위를 가진다. 이 큐 역시 런타임 환경에서 관리된다.이벤트 루프 (Event Loop):
setTimeout
과 Promise)setTimeout(callback, delay)
: setTimeout
함수는 호출 스택에 쌓인 후 웹 API로 전달된다. 웹 API는 타이머를 시작하고, 메인 스레드는 블로킹되지 않고 다음 코드를 실행한다. delay
시간이 지나면 callback
함수는 매크로태스크 큐로 이동하여 대기한다.new Promise((resolve, reject) => { /* ... */ }).then(callback)
: Promise
생성자 내의 동기 코드는 즉시 실행된다. resolve
나 reject
가 호출되면, .then()
에 등록된 callback
함수는 마이크로태스크 큐로 이동하여 대기한다.메시지 큐에 콜백을 실행 중이고 아직 많이 남아있을 때 동기 코드가 생기면 어떻게 되는가?
매크로태스크 큐에서 하나의 콜백 함수가 호출 스택으로 옮겨져 실행되는 동안, 그 콜백 함수 안에서 새로운 동기 코드가 생성되거나 실행된다면, 그 동기 코드는 해당 콜백의 실행 흐름 내에서 먼저 완료된다. 콜백 함수가 완전히 종료되고 호출 스택이 다시 비워져야 이벤트 루프가 큐에 있는 다음 콜백을 가져올 수 있다. 즉, 하나의 매크로태스크가 실행되는 동안은 동기적으로 동작한다.
호출 스택은 메시지 큐에서 하나씩 빼서 넣는가?
매크로태스크 큐(Callback Queue)에서는 이벤트 루프가 한 번에 하나의 콜백만 호출 스택으로 옮긴다. 하지만 마이크로태스크 큐에서는 호출 스택이 비워진 직후 큐에 있는 모든 마이크로태스크를 한 번에 호출 스택으로 옮겨 실행시킨다.
큐가 왜 두 개 있는가? (매크로태스크 큐 vs. 마이크로태스크 큐)
우선순위가 다른 두 가지 주요 큐가 존재한다.
setTimeout
, setInterval
, I/O (Node.js), UI 렌더링, requestAnimationFrame
, MessageChannel
콜백, DOM 이벤트 등. 비교적 큰 작업 단위를 가진다..then()
, .catch()
, .finally()
콜백, queueMicrotask()
, MutationObserver
콜백 등. 매크로태스크보다 높은 우선순위를 가지며, 현재 실행 중인 매크로태스크가 완료된 후 다음 매크로태스크로 넘어가기 전에 모든 마이크로태스크가 실행된다.queueMicrotask()
는 주로 언제 사용하는가?
queueMicrotask()
는 주어진 콜백 함수를 마이크로태스크 큐에 추가한다. 다음과 같은 경우에 사용한다:
.then()
과 동일한 시점에 비동기적으로 코드를 실행해야 할 때 사용한다.이러한 구성 요소들이 긴밀하게 상호작용하면서, 자바스크립트는 단일 스레드 언어임에도 불구하고 복잡하고 동적인 웹 애플리케이션을 효율적으로 구축한다.