이벤트 루프(Event Loop) 정의는 아주 간단하다. 이벤트 루프는 태스크가 들어오길 기다렸다가 태스크가 들어오면 이를 처리하고, 처리할 테스크가 없는 경우엔 잠드는, 끊임없이 돌어가는 자바스크립트 내 루프이다.
자바스크립트 엔진이 돌아가는 알고리즘을 일반화하면 다음과 같다.
바로 이 알고리즘이 우리가 브랑줘를 사용해 인터넷을 서핑할 때 돌아가는 알고리즘이다. 이렇게 자바스크립트 엔진은 대부분의 시간 동안 아무런 일도 하지 않고 쉬고 있다가 스크립트나 핸들러, 이벤트가 활성화될 때만 돌아간다.
그렇다면 자바스크립트 엔진을 활성화하는 테스크엔 어떤 것들이 있을까?
테스크는 하나의 집합을 이룬다. 자바스크립트 엔진은 집합을 이루고 있는 테스크들을 차례대로 처리하고, 새로운 테스크가 추가될 때까지 기다린다. 테스크를 기다리는 동안엔 CPU 자원 소비는 0에 가까워지고 엔진은 잠들게 된다.
새로운 테스크는 엔진이 바쁠 때 추가될 수도 있다. 이때 이 테스크는 큐에 추가된다. 이렇게 테스크가 추가되는 큐는 V8 용어로 '매크로테스크 큐(macrotask queue)'라고 부른다.
2개의 큐 모두 콜백함수가 들어간다는 점에서 동일하지만 어떤 함수를 실행하느냐에 따라 어디로 들어가는 지가 달라진다. 또한 명칭은 큐(Queue)이지만 실제 우리가 아는 자료구조의 쿠와는 다르다. 엄밀히 말하자면 우선순위 큐(Priority Queue)라고 할 수 있는데, 이벤트 루프가 2개의 큐에서 테스크를 꺼내는 조건이 "제일 오래된 테스크"이기 때문이다.
콜백함수를 테스크 큐에 넣는 함수들
콜백함수를 마이크로테스크 큐에 넣는 함수들
익숙한 함수인 Web API의 setTimeout()의 콜백함수가 테스크 큐에 들어가고 Promise의 콜백함수가 마이크로테스크 큐에 들어간다는 것을 알 수 있다.
결론부터 말하자면, 마이크로테스크가 먼저이다.
이벤트 루프는 마이크로테스크 큐의 모든 테스크들을 처리한 다음, 테스크 큐의 테스크들을 처리한다. 따라서, Promise의 콜백함수가 setTimeout()의 콜백함수보다 먼저 처리된다.
console.log('콜 스택!');
setTimeout(() => console.log('태스크 큐!'), 0);
Promise.resolve().then(() => console.log('마이크로태스크 큐!'));
콜 스택!
마이크로태스크 큐!
태스크 큐!
마이크로테스크 큐의 콜백함수가 먼저 처리되는 것을 볼 수 있다. 그렇다면 여기서 처음 나오는 console.log()는 언제 처리되는 것일까? 처음 스크립트가 로드될 때 "스크립트 실행"이라는 테스크가 먼저 테스크 큐에 들어간다. 그리고 나서 이벤트 루프가 테스크 큐에서 해당 테스크를 가져와 콜 스택을 실행하는 것이다. 즉, 콜 스택에는 이미 GEC(Global Execution Context)가 생성되어 있는 상태에서 "스크립트 실행"이라는 테스크를 실행하게 되면 그제서야 GEC에 속한 코드가 실행되는 방식이다.