노션에 정리하며 공부했던 기록들 옮깁니다.
2개의 큐 모두 콜백함수가 들어간다는 점에서 동일하지만 어떤 함수를 실행하느냐에 따라 어디로 들어가는지가 달라진다. 또한 명칭은 큐 (Queue) 이지만 실제 우리가 아는 자료구조의 큐와는 다르다.
엄밀히 말하자면 우선순위 큐 (Priority Queue) 라고 할 수 있는데, 이벤트 루프가 2개의 큐에서 태스크를 꺼내는 조건이 “제일 오래된 태스크” 이기 때문이다.
(동작방식을 확인하고 싶다면 HTML 스펙 을 보자)
Web API의 setTimeout()
의 콜백함수가 태스크 큐에 들어가고 Promise
의 콜백함수가 마이크로태스크 큐에 들어간다는 것을 알 수 있다.
이벤트 루프는 각 콜백함수를 태스크/마이크로태스크 큐에서 꺼내쓰는 것인데, 그렇다면 어떤게 먼저일까?
이벤트 루프는 마이크로태스크 큐의 모든 태스크들을 처리한 다음, 태스크 큐의 태스크들을 처리한다.
따라서, Promise
의 콜백함수가 setTimeout()
의 콜백함수보다 먼저 처리된다.
console.log('콜 스택!');
setTimeout(() => console.log('태스크 큐!'), 0);
Promise.resolve().then(() => console.log('마이크로태스크 큐!'));
//콜 스택!
//마이크로태스크 큐!
//태스크 큐!
볼 수 있다시피, 마이크로태스크 큐의 콜백함수가 먼저 처리된다.
그렇다면 여기서 처음 나오는 console.log()
는 언제 처리되는 것일까?
처음 스크립트가 로드될 때 “스크립트 실행” 이라는 태스크가 먼저 태스크 큐에 들어간다. 그리고 나서 이벤트 루프가 태스크 큐에서 해당 태스크를 가져와 콜 스택을 실행하는 것이다.
즉, 콜 스택에는 이미 GEC(Global Execution Context)가 생성되어 있는 상태에서 “스크립트 실행” 이라는 태스크를 실행하게 되면 그제서야 GEC에 속한 코드가 실행되는 방식이다.
그럼 하나하나 어떻게 동작하는지 그림으로 살펴보자.
제일 먼저, “스크립트 실행” 태스크가 태스크 큐에 들어가게 된다.
이벤트 루프가 그 태스크를 가져와서 로드된 스크립트를 실행시킨다. 따라서 console.log
실행된다.
그 다음, setTimeout()
이 콜 스택으로 가고 브라우저가 이를 받아서 타이머를 동작시킨다.
타이머가 끝나면 setTimeout()
의 콜백함수를 태스크 큐에 넣는다.
Promise
가 콜 스택으로 가고 콜백함수를 마이크로태스크 큐에 넣는다.
이벤트 루프는 마이크로태스크 큐에서 제일 오래된 태스크인 Promise
의 콜백함수를 가져와 콜 스택에 넣는다.
Promise
의 콜백함수가 끝나고 태스크 큐에서 제일 오래된 태스크인 setTimeout()
의 콜백함수를 가져와 콜 스택에 넣는다.
setTimeout()
의 콜백함수가 끝나면 콜 스택이 비게 되고 프로그램이 종료된다.