js라는 것은 항상 엔진 위에서 동작하죠. 그리고 js는 싱글 스레드 방식으로 동작합니다.
이 말은 js를 실행시키는 엔진의 메모리에 실행 컨텍스트 스택(콜스택)이 하나임을 의미합니다.
이 실행 컨텍스트 스택 안에서 최상위에 존재하는 실행 컨텍스트만 실행 중인 상태이며, 나머지(태스크)는 대기 중인 상태인 것이죠.
즉, js(이하 js 엔진과 동일)는 한 번에 하나의 함수만 실행 가능합니다.
그런데 js에서도 비동기 처리가 가능합니다.
비동기 처리를 하면 해당 작업의 완료를 기다리지 않고 다른 작업을 실행시킬 수 있습니다.(즉 블로킹이 발생하지는 않지만, 태스크의 작업 완료 순서가 보장되지는 않죠)
분명 한 번에 하나의 함수만 실행 가능하다고 했는데, 이게 어떻게 가능한 것일까요?
그건 바로 브라우저나 node.js가 가지고 있는 멀티 스레드에 일을 위임하기 때문입니다.
(브라우저나 node.js는 js를 실행 시키는 엔진이 구동되는 환경입니다.)
끝입니다.
...사실 아닙니다.
보통 비동기 처리를 수행하는 함수는 콜백 함수 또는 프로미스를 활용합니다.
이는 비동기 함수의 작업이 완료된 시점에 그 결과를 활용하기 위함입니다.
브라우저에는 비동기 함수의 콜백 함수를 보관하는 태스크 큐와 프로미스의 후속 처리 메서드의 콜백 함수를 보관할 마이크로 태스크 큐가 모두 내장되어있습니다.
그리고 이러한 자원을 사용해 함수의 실행을 관리하는 이벤트 루프가 내장되어있습니다.
(node.js에서는 libuv라이브러리가 이 역할을 합니다.)
js는 이러한 자원들을 활용하여 비동기 작업을 처리합니다.
비동기 작업의 처리 과정은 아래와 같습니다.

- setTimeout, fetch 등 비동기 함수가 호출될 때, js 엔진의 콜스택에 함수의 실행 컨텍스트가 푸시됩니다. 이 함수는 내부적으로 브라우저의 Web API나 Node.js런타임의 libuv를 호출하고 콜스택에서 팝됩니다. (비동기 작업을 기다리며 남아있지 않습니다.)
- 브라우저의 Web API / Node.js런타임의 libuv는 비동기 작업을 수행합니다. 이후 작업이 완료되면, 타이머나 이벤트 핸들러 등의 콜백은 태스크 큐에, 프로미스의 후속 처리 콜백은 마이크로 태스크 큐에 푸시 합니다.
- 이벤트 루프는 js 엔진의 콜스택이 비었는지 계속 확인하다가, 콜스택이 비었을 때 큐에서 콜백을 꺼내 콜스택에 푸시합니다.
-> 이렇게 해서 js 엔진의 콜스택에 푸시된 콜백함수(= 실행 컨텍스트)는 js 엔진의 콜스택 최상위에서 실행되고, 종료 후 콜스택에서 팝됩니다.
이러한 과정으로 싱글 스레드인 js에서 비동기 처리가 가능해지고, 여러 작업이 동시에 수행되는 것 처럼 보이는 것입니다.
--
브라우저에서 실행되기 위해 만들어진 언어인 만큼, 브라우저 런타임에 큰 의존도를 보이는 js만의 독특한 특징을 알 수 있었습니다 👍🏻
참고 자료: