티스토리 뷰

이벤트 루프란 결론부터 말하자면 호출 스택이 비워질 때마다 콜백 큐에서 호출할 콜백함수가 있는지 반복하여 확인 후 있으면 콜백함수를 호출하여 실행하는 역할을 하는 것이다.
이벤트 루프의 정의를 들었을 때 이해가 안 간다면 아마 호출 스택, 콜백 큐 등을 구동하는 브라우저 환경에 대한 이해가 확실하지 않아 그럴 수 있다.
일단 자바스크립트 엔진을 구동하는 브라우저 환경을 그림으로 나타내면 다음과 같다.

구성요소는 다음과 같다.
heap : 메모리 할당이 이루어지는 곳
호출 스택(call stack) : 자바스크립트는 코드가 실행되면 순서대로 실행될 코드들이 쌓여 처리되는 곳
web APIs : 실행시키고자 하는 코드 중에 xmlHttpRequest(), setTimeout(), setInterval() 등의 API가 있을 경우 이 API들이 있는 곳
콜백 큐(callback queue) : web APIs에 있는 API들이 실행될 준비가 완료되면 해당 API는 콜백 큐에 추가되며 호출 스택에서 모든 코드들이 실행이 완료되어 비어 있으면 이벤트 루프가 콜백 큐에서 먼저 실행준비가 완료된 함수를 하나씩 실행시키기 위해 호출 스택으로 이동시키고자 꺼내가는 곳
그럼 아래의 예시 코드를 통해 브라우저 환경이 어떻게 돌아가는지 알아보자!
console.log(1);
console.log(2);
console.log(3);
우선 console.log(1)이 호출 스택에 쌓이고 실행되며 실행이 완료되면 사라진다.
그 다음에 console.log(2)와 console.log(3)도 호출 스택에 쌓이고 마지막에는 호출 스택에서 사라지는 것이 순서대로 진행되며 결과는 다음과 같다.
1
2
3
이처럼 자바스크립트는 하나의 함수가 실행되면 해당 함수의 실행이 완료될 때까지 다른 함수들이 실행되는 작업이 이루어질 수 없는 "Run-to-Completion" 방식을 갖고 있다.
그럼 다음과 같은 코드는 어떤 실행과정을 거치는지 알아보자!
function number2() {
console.log(2);
}
console.log(1);
setTimeout(number2, 0);
console.log(3);
console.log(4);
일단 실행결과는 다음과 같다.
1
3
4
2
setTimeout()함수는 첫번째 인자에서 선언한 함수를 두번째 인자에서 설정한 특정 시간 경과 후에 실행된다고 알고 있기 때문에 0으로 설정할 경우 바로 코드가 실행되는 것으로 오해하고 있는 경우가 있을 수 있다.
하지만 위 코드의 실행과정을 이해한다면 왜 1,2,3,4가 아닌 1,3,4,2인지 이유를 알게 될 것이다.
우선 console.log(1)이 호출 스택에 추가되며 실행되고 실행완료 시 사라지며 최초로 1이 출력된다.
그리고 다음 코드인 setTimeout(number2, 0)이 호출 스택에 추가되고 해당 함수는 비동기 함수이기 때문에 자바스크립트 엔진은 바로 실행되지 않는다고 판단하여 webAPIs로 이동시키고 setTimeout은 호출 스택에서 사라진다.
이때 호출 스택에서 실행이 완료되지 않았기 때문에 2는 출력되지 않는다.
그 다음 코드인 console.log(3)이 호출 스택에 추가되어 실행되고 호출스택에서 사라지는 동안 webAPIs로 이동된 setTimeout(number2, 0)은 0ms후에 콜백 큐에 추가된다.
콜백 큐에 추가된 setTimeout(number2, 0)은 이벤트 루프에 의해 호출 스택으로 이동되어 2가 출력될 수 있지만 아직 호출 스택에는 처리해야 할 console.log(4)가 있기 때문에 대기를 타게 된다.
호출 스택에서 console.log(4)는 호출 스택에서 실행이 완료되어 4가 출력이 되고 호출 스택에서 사라져 호출 스택은 빈 상태가 된다.
이때 이벤트 루프는 호출 스택에서 실행중인 코드가 없는지 그리고 콜백 큐에서 대기중인 함수들이 있는지 확인하고 두 조건을 만족하면 콜백 큐에서 먼저 실행준비가 완료된 함수를 호출 스택으로 이동시켜 실행되도록 한다.
호출 스택에서 실행이 완료된 number2()함수는 2를 출력하고 호출 스택에서 사라지게 된다.
그렇기 때문에 실행결과는 1,3,4,2가 되는 것이다.
한편 다음과 같은 코드를 실행하게 되면 실행결과는 어떻게 나올까 생각해보자!
console.log(1);
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve()
.then(() => {
console.log('3');
})
.then(() => {
console.log('4');
});
setTimeout과 Promise가 같은 비동기이고 setTimeout의 실행시간은 0ms이고 Promise보다 코드가 위에 위치하고 있으니 1,2,3,4가 되지 않을까 생각할 수도 있다.
실행결과는 다음과 같다.
1
3
4
2
이유는 결론부터 말하자면 콜백 큐에는 마이크로 태스크 큐(Microtask Queue), 태스크 큐(Task Queue)가 있는데 setTimeout의 경우 태스크 큐에 추가되고 Promise는 태스크 큐보다 먼저 실행되는 '마이크로 태스크'에 추가되기 때문이다.
자바스크립트의 API들과 비동기 함수들은 사실 종류에 따라 각기 다른 큐에 추가되지만 여기에서는 마이크로 태스크 큐와 태스크 큐만 언급하겠다.
다시 설명으로 돌아와서 마이크로 태스크는 태스크 큐보다 높은 우선 순위를 갖고 있다.
태스크 큐에 대기중인 함수가 있다고 해도 이벤트 루프는 우선적으로 마이크로 태스크에 함수가 있는지 확인한다.
실행과정을 설명하자면 다음과 같다.
우선 console.log(1)이 호출 스택에서 실행되고 실행 완료후 사라지며 1이 출력된다.
다음에 setTimeout()이 호출 스택에 추가되고 webApIs로 이동된다.
그리고 0ms 후에 태스크 큐에 추가된다.
다음으로 then()이 호출 스택에 추가되며 webApIs로 이동 후 마이크로 태스크 큐에 추가된다.
그 후 이벤트 루프가 태스크 큐가 아닌 마이크로 태스크를 우선적으로 함수가 있는지 확인한다.
그리고 나서 마이크로 태스크 큐에 있던 then()안의 console.log('3')이 호출 스택으로 이동되어 실행 후 실행이 완료 후 호출 스택에서 사라지며 3이 출력된다.
다음으로 호출 스택에 console.log('4')를 갖고 있는 then()이 추가되고 webAPIs로 이동된다.
그 후 마이크로 태스크 큐에 console.log('4')를 갖고 있는 then()이 추가되며 이벤트 루프가 마이크로 태스크 큐에 있던 then()을 호출시키기 위해 호출 스택으로 이동시키며 호출 후 4가 출력되며 호출 스택에서 사라진다.
호출 스택은 빈 상태이며 이벤트 루프는 이번에도 마이크로 태스크에 함수가 있는지 확인한다.
이번에는 태스크 큐에 함수가 있는지 확인한다.
태스크 큐에 setTimeout이 있다는 것을 확인한 이벤트 루프는 해당 함수를 실행시키기 위해 호출 스택으로 이동시킨 후 해당 함수가 실행되어 2를 출력하며 호출 스택에서 사라지게 된다.
이번 글을 올리면서 이벤트 루프를 제대로 이해하는 것이 자바스크립트의 비동기를 효율적으로 제어할 수 있을 것이라고 생각하였고 자바스크립트를 공부하는 개발자 입장에서는 매우 중요한 개념이라는 것을 느꼈다.
반응형
'Languages > JS' 카테고리의 다른 글
자료형 (0) | 2021.10.08 |
---|---|
가비지컬렉션 (0) | 2021.10.08 |
비동기 2편(async, await) (0) | 2021.05.13 |
비동기 1편(콜백함수, promise) (0) | 2021.05.13 |
클로저(closure) (0) | 2021.05.13 |