function counting() {
let i = 0;
for ( i=0; i<5; i++) {
setTimeout(function () {
console.log(i);
}, i * 100);
}
}
위 코드를 실행하면 0,1,2,3,4가 출력되는 것이 아니라 5,5,5,5,5가 출력된다.
이 현상을 이해하기 위해서는 먼저 자바스크립트의 비동기 처리방식을 알아야 하는데,
What the heck is the event loop anyway? 영상을 보고 비동기 처리방식을 정리해보았다.
자바스크립트는 싱글 스레드 런타임을 가진다. 때문에 네트워크 요청이나 이미지 프로세싱 같이 시간이 오래걸리는 작업이 있다면, 작업이 끝날때까지 아무런 작업을 할 수 없어야 한다. 하지만 브라우저가 런타임 이상의 일을 하는 덕분에 다른 작업을 동시에 진행할 수 있게 된다.
먼저, ajax요청이나 setTimeout은 V8(자바스크립트엔진)이 아닌 브라우저에서 제공하는 런타임 환경에만 존재하는 별도의 API이다.
때문에 스택이 아닌 Web API에서 별도로 진행되는데, Web API는 타이머가 끝난 후 콜백을 테스크 큐에 넣는다. 이때 이벤트루프는 스택이 비면 테스크큐에 있는 작업을 스택에 넣어주고, 자바스크립트는 스택에 들어온 작업을 실행한다.
영상에 나왔던 그림을 바탕으로 위의 함수를 실행시킨다면,
스택에 있는 for문이 실행되면서 i가 5가 되는 동안에 setTimeout은 Web API에서 실행된다.
이후 Web API에서 실행된 setTimeout은 테스크큐에 넣어지고, 스택이 비어진 것을 확인한 이벤트루프가 테스크큐의 작업을 스택에 넣어 i를 console.log 하게 되는 것이다.
이러한 원리를 이용해 setTimeout 0 같이 의미없는 것처럼 보이는 함수를 사용할 때가 있는데, 스택이 비어있을때까지 기다리도록 하는 일종의 꼼수인 것이다.
더 나아가 렌더링을 해주는 렌더큐(Render Queue)를 살펴보면,
렌더도 하나의 콜백처럼 행동하기 때문에 스택에 코드가 있으면 렌더링을 하지 못하게 되는데, 그렇게 된다면 화면을 선택하거나 선택했을때 반응을 볼 수 없게 되는 아주 질나쁜 사용자경험을 하게 된다.
때문에 렌더큐는 콜백큐보다 높은 우선순위를 가지며, 1초에 60번씩 렌더링 할것이 있는지 확인하고 스택이 깨끗하다면 큐에 렌더를 넣어 렌더링 하게 된다.
더, 더 나아가 Microtask Queue와 Animation frames까지 살펴보자면, 앞선 이벤트루프 설명에는 테스크큐(Task Queue)만을 설명했지만 사실 큐(Queue)에는 Task Queue, Microtask Queue, Animation frames이 있다. 간단한 정의를 살펴보기로 하자.
Microtask Queue는 실행순서를 정의하기 위한 콜백함수의 중첩을 가독성 좋게 표현하는 Promise와 돔 변경을 감지하는 Mutation observer에 등록된 콜백이 담기는 곳이다.
Animation frames은 Web API인 requestAnimationFrame() 함수를 호출할 때 그 안에 등록된 콜백이 쌓여지는 곳이다. 보통 애니메이션이나 게임과 같이 실시간으로 업데이트 되어야 하는 곳에 쓰인다.
Event Loop는 크롬 기준, Microtask Queue → Animation frames → Taske Queue 순으로 작업을 진행하지만 브라우저마다 차이점이 있을 수 있다.
참고사이트: What the heck is the event loop anyway?, GeekCorder, velog@thms200