자바스크립트는 단일스레드로 동작한다고 한다. 나는 이를 ‘자바스크립트는 한 번에 하나의 동작만 실행하는구나’ 정도로 간단히 이해하고 있었는데, 비동기 실행을 배우고나서 궁금한게 생겼다.
위 그림 중 ‘비동기 실행’ 그림에서 비동기 함수인 fetch 함수로 요청을 보내고 받기를 기다리는 과정과, console.log 를 출력하는 동작은 동시적으로 일어나고 있는 것 처럼 묘사되어 있었기 때문이다. 자바스크립트가 단일스레드로 동작한다면, 동시에 진행되고 있는 두 개의 동작 중 fetch 동작은 누가 처리하고 있는건지 궁금했다.
자바스크립트 엔진은 브라우저(혹은 node.js)의 도움을 받고 있었는데, 이 도움으로 위와 같은 비동기 실행이 가능한 것이었다. 예시로 조금 더 자세히 살펴볼 수 있다.
function foo(){
console.log('foo');
}
function bar(){
console.log('bar');
}
setTimeout(foo, 0);
bar;
✅ bar, foo 순으로 콘솔창에 출력된다.
비동기 동작 처리가 어떻게 일어나는지 잘 알지 못한 상태에서 이 코드를 보면 setTimeout(foo, 0);
가 0초뒤에 foo라는 콜백함수를 실행하라는 의미이므로 일반 함수가 실행되는 것과 같이 foo 함수가 곧바로 실행되고, 이어 bar함수가 실행될 것이므로 콘솔창에는 foo, bar가 순서대로 나타날 것이라고 생각할 수도 있다. (왜냐하면 내가 그랬다..)
하지만 위 소스코드는 평가과정을 거쳐 다음과 같은 순서대로 실행된다.
setTimeout 함수를 호출한다.
브라우저에게 콜백 함수와 호출 스케줄링(몇 초 뒤에 태스크 큐에 푸시해야하는 함수인지)을 전달한다.
setTimeout(foo, 0)
의 경우 “foo라는 콜백함수를 4ms 뒤에 태스크 큐로 푸시하고 대기시켜라”고 전달하게 된다. 0s가 아니라 4ms인 이유는, 최소지연시간이 4ms이기 때문이다. 4ms 미만의 지연시간을 지정했기 때문에 자동으로 4ms의 지연시간이 지정된 경우이다.
그러면 자바스크립트 엔진의 역할은 끝난다. foo
가 4ms안에 돌아오든 말든 바로 setTimeout 함수를 실행한 실행 컨텍스트를 pop하고 다음 문(statement)인 bar 함수 실행 컨텍스트를 생성한다.
브라우저로 넘어간 콜백함수는 자바스크립트이 일이 다 끝날 때 까지 기다린다
자바스크립트 엔진이 전달한 내용에 따라 브라우저는 4ms뒤에 foo
라는 콜백함수를 콜백큐로 푸시하게 된다.
콜백큐에 전달된 콜백함수 foo
는 이제 자바스크립트 엔진으로 넘어가 실행되기를 기다린다. 언제까지 기다려야하는지는 브라우저의 이벤트 루프event loop
이 알려줄 것이다.
자바스크립트 엔진이 할 일을 모두 마쳤다!
‘자바스크립트 엔진이 할 일을 모두 마쳤다’는 것은, 정확히 말하자면 자바스크립트 엔진의 콜스택이 비었다는 것이다.
이벤트 루프는 콜 스택에 현재 실행중인 실행 컨텍스트가 있는지 계속 확인하다가, 콜스택이 비어있고, 마침 콜백큐에서 foo
처럼 대기중인 콜백함수가 있다면 그 때 콜백큐의 콜백함수를 자바스크립트 엔진의 콜스택으로 푸시하는 역할을 한다. 이 코드의 경우, bar()
함수가 자바스크립트 엔진에 의해 실행을 모두 마치고 콜스택에서 pop되면 콜스택은 완전히 비게된다. 이 순간! 브라우저의 콜백큐에서 얌전히 기다리고 있던 foo()
콜백함수가 자바스크립트로 넘어가 실행된다.
위 그림에서 알 수 있듯, 콜백큐에는 두 종류가 있다고 한다. (microtask queue, macrotaks queue)
자바스크립트의 비동기동작은 잠깐 콜백함수를 맡아주는 브라우저의 도움으로 가능하다!