우리는 이미 실행 컨텍스트에 관한 개념을 익혔고, 비동기 처리는 다양한 곳에서 사용되고 있다. 그러나 자바스크립트는 한 번에 하나의 태스크만 동작하는(하나의 실행 컨텍스트만을 가지는) 싱글 스레드 언어이다. 그렇다면 어떤 방식으로 비동기 처리가 이루어질까?

이 그림에서 stack (call stack)은 실행 컨텍스트를 뜻하고, 소스코드의 평가와 실행을 제외한 처리를 webapis(DOM API, Timer function, HTTP request 등등 호스트 객체. 실행 환경에서 제공하는 객체를 뜻한다)가 담당한다. 그리고 event loop은 call stack의 실행 상태와 task queue에 대기중인 함수(콜백 함수, 이벤트 핸들러 등)가 있는지를 비교하여 call stack이 비었고 task queue에 대기중인 함수가 있다면 First in First Out 방식으로 함수를 이동시킨다. 이 처리 과정은 비동기로 작동한다. 이해를 돕는 예제를 살펴보자.
function foo(){
console.log('foo1')
}
function foo2(){
console.log('foo2')
}
function foo3(){
console.log('foo3')
}
function bar(){
console.log('bar')
}
function wait(){
console.log('wait');
}
function sleep(func,delay){
const delayUntil = Date.now()+delay;
while(Date.now()<delayUntil);
func();
}
setTimeout(foo,10);
setTimeout(foo2,0); //4ms
setTimeout(foo3,20);
bar();
sleep(wait,1000);
이 예제의 출력 결과가 어떻게 나올까?
bar
wait
foo2
foo1
foo3
순서로 출력된다. 그 과정을 순서대로 알아보면,
- 전역 코드가 평가되어 전역 실행 컨텍스트가 생성되어 콜 스택에 푸쉬된다.
- 전역 코드가 setTimeout 함수를 만난다. 이때 setTimeout 함수의 실행 컨택스트가 생성되고 푸쉬되어 현재 실행중인 실행 컨텍스트가 되고, webapis의 타이머 함수도 실행 컨텍스트를 생성한다.
- setTimeout 함수가 실행되면 콜백 함수를 호출 스케줄링하고 종료되어 콜 스택에서 팝되어 사라진다. 여기서 호출 스케줄링(타이머 설정, 타이머 만료시 콜백 함수를 태스크 큐에 푸쉬하는 것)은 브라우저(webapis)가 담당한다.
- 브라우저가 수행하는 4-1과 자바스크립트 엔진이 실행하는 4-2가 병행 처리된다.
4-1. 브라우저는 타이머를 설정하고 타이머의 만료를 기다린다. 이후 타이머 만료시 콜백 함수 foo가 태스크 큐에 푸쉬된다. 딜레이가 4ms 이하라면 최소 지연시간 4ms가 지정되어 4ms 후에 foo가 푸쉬되어 대기한다. 여기서 알아야 할 점이 정확히 그 지연시간 이후에 호출되지 않을 수 있다는 것이다. 콜 스택이 비어야 호출하므로 시간의 차이가 생길 수 있다.
4-2. bar 함수가 호출되어 실행 컨텍스트를 생성, 콜 스택에 푸쉬되어 현재 실행중인 실행 컨텍스트가 된다. 이후 종료되어 팝되어 사라졌을 때 10ms가 경과했다면 foo는 아직 태스크 큐에서 대기중일 것이다.- 전역 코드 실행 종료, 전역 실행 컨텍스트가 팝되어 콜 스택엔 아무 것도 없다.
- 이벤트 루프가 비어 있음을 감지한다. 그 후 태스크 큐에서 대기 중인 콜백 함수를 불러와 실행 컨텍스트를 생성한다.
결과에서 알 수 있듯이 이벤트 루프는 전역 코드가 실행이 끝났을 때, 즉 실행 컨텍스트가 비었을 때 FIFO 방식으로 함수를 가져온다. 위 예제에서는 foo1이 먼저 실행되었지만 이것은 비동기로 처리되고 아래의 foo2의 지연시간이 더 짧기 때문에 제일 먼저 태스크 큐에 들어갔을 것이다. 그러므로 출력 순서가 bar > wait > 2 > 1 > 3이 된다고 생각하자. 실제 출력은 bar가 출력되고 1초 기다린 후에 wait, 2, 1, 3이 순차적으로 출력된다. sleep는 비동기로 처리되지 않기 때문에 코드 실행 자체를 1000ms 동안 멈춘다는 것을 주의하자.