자바스크립트와 비동기 프로그래밍
자바스크립트 코드의 평가와 실행을 담당하는 자바스크립트 엔진은 싱글 스레드 방식으로 돌아가기 때문에 자바스크립트는 싱글 스레드 언어라고 부를 수 있다. 싱글 스레드는 한 번에 하나의 태스크만 처리할 수 있다는 것을 의미한다.
function wait(ms) {
const until = Date.now() + ms
while (Date.now() <= until) {}
return
}
wait(3000)
console.log('hello world!')
s
자바스크립트 엔진 관점에서 위 코드를 살펴보자.
- 자바스크립트 엔진은 런타임 이전에 전역 소스 코드를 평가하여 실행 컨텍스트를 생성한다.
- 전역 실행 컨텍스트를 콜 스택에 푸시한다.
- 자바스크립트 엔진은 함수 실행 문을 만나면 함수 실행 컨텍스트를 생성하여 콜 스택에 푸시한다. 이때 콜 스택 가장 상위에 있는 wait 함수의 실행 컨텍스트가 현재 실행 컨텍스트가 된다.
- 자바스크립트 엔진은 싱글 스레드이기 때문에 wait 함수 실행 컨텍스트가 콜 스택에서 POP되기 전에
console.log("hello world!");
를 실행하지 않는다. 즉 블락킹 된다. - 3초가 지나 함수의 실행이 끝나면 콜스택에서 wait함수 컨텍스트를 POP한다.
console.log("hello world!")
를 호출한다.- 전역 실행 컨텍스트를 콜스택에서 POP한다.
자바스크립트 엔진은 함수의 호출과 실행을 하나의 콜 스택(또는 실행 컨텍스트 스택)으로 관리한다.
하지만 자바스크립트로 작성한 많은 앱들은 블락킹 되지 않고 여러 개의 일을 한번에 처리하는 것처럼 보인다. 이는 태스크를 동기가 아닌 비동기로 처리하기 때문이다.
비동기로 처리한다는 것은 다음 대기중인 태스크를 처리하기 전에 특정 태스크가 완료될 때 까지 기다리지 않는다는 것을 의미한다.
자바스크립트 런타임 환경(브라우저 또는 Nodejs)이 제공하는 setTimeout, setInterval, HTTP request, 이벤트 헨들러가 비동기 처리 방식으로 동작한다.
우리는 여기서 자바스크립트 엔진과 자바스크립트 런타임 환경(이하 브라우저라 하겠다.)을 명확히 구분해서 알아야 한다. 브라우저가 자바스크립트 엔진을 내장하고 있지만 둘의 역할을 구분해서 알아야 한다. 자바스크립트 엔진은 자바스크립트 코드를 평가하고 실행하는 역할을 한다. 구글의 V8 자바스크립트 엔진은 크게 콜 스택과 힙으로 나눌 수 있다. 콜 스택은 코드를 순차적으로 실행하는 역할을 하고 힙은 메모리를 할당하는 공간이다. 즉, 자바스크립트 엔진은 비동기 작업과 연관이 없다.
비동기 작업을 처리하는 것은 자바스크립트 엔진이 아니라 브라우저와 같은 자바스크립트 런타임 환경이다. 특히 브라우저의 이벤트 루프와 태스크 큐에 의해서 처리된다.
태스크 큐란 비동기 작업의 콜백함수 또는 이벤트 헨들러가 일시적으로 저장되는 데이터 구조다.
이벤트 루프는 콜 스택과 태스크큐를 번갈아 확인하면서 만약 콜 스택이 비어있고 태스크 큐에 대기중인 태스크가 있다면 dequeue하여 콜 스택에 푸시한다.
이 처럼 자바스크립트의 비동기 작업은 브라우저의 이벤트 루프와 태스크 큐에 의해 처리 된다.
function foo() {
// Do something
}
setTimeout(foo, 0)
console.log("hello world)
위 코드가 어떤 과정을 거쳐 실행하는지 살펴보자.
- 전역 실행 컨텍스트가 콜스택에 푸시된다.
- setTimeout의 함수컨텍스트가 콜스택에 푸시된다.
- 이때 브라우저는 타이머를 설정하고 타이머가 완료되면 콜백함수를 태스크 큐에 enqueue한다.
- 호출 스케줄링은 비동기 작업이기 때문에 블락킹되지 않는다. 따라서
console.log("hello world)
를 바로 실행한다. - 전역 실행 컨텍스트를 콜스택에서 pop한다.
- 이때 이벤트 루프는 콜 스택이 비어있는 것을 감지하여 태스크 큐에 있는 대기중인 태스크를 콜스텍에 푸시한다.
- 콜백함수를 실행하고 콜 스택에서 POP한다.