Event Loop
Event Loop & Concurrency
- 브라우저는 single-thread에서 event-driven 방식으로 동작함
- thread가 하나이므로, 하나의 작업(task)만 처리할 수 있음
- 실제 동작하는 웹 어플리케이션은 많은 작업을 동시에 처리되는 것처럼 동작함 : 동시성(Concurrency)
- Event Loop가 JavaScript의 동시성을 지원함
- 브라우저의 환경을 그림으로 표현하면 아래와 같음

JavaScript 엔진
- JavaScript 엔진은 단순히 작업이 요청되면 Call Stack을 사용하여 작업을 순차적으로 실행함
- JavaScript 엔진은 크게 2개의 영역으로 나뉨
- Call Stack
- 작업이 요청되면(함수가 호출되면) 요청된 작업이 순차적으로 Call Stack에 쌓임
- JavaScript는 단 하나의 Call Stack을 사용하므로, 해당 task가 종료되기 전까지는 다른 task를 수행할 수 없음
- Heap
- 동적으로 생성된 객체 인스턴스가 할당되는 영역
Event Queue(Task Queue) & Event Loop
- 동시성을 지원하기 위해 필요한 비동기 요청(이벤트 포함) 처리는 JavaScript 엔진을 구동하는 환경 즉 브라우저(또는 Node.js)가 담당함
- Event Queue(Task Queue)
- 비동기 처리 함수의 콜백함수, 비동기식 이벤트 핸들러, Timer 함수(setTimeout, setInterval)의 콜백함수가 보관되는 영역
- Event Queue에 보관된 콜백함수들은 Call Stack에 비어졌을 때 Event Loop에 의해 순차적으로 Call Stack으로 이동되어 실행됨
- Event Loop
- Call Stack 내에서 현재 실행중인 작업이 있는지, Event Queue에 작업이 있는지 반복하여 확인함
- Call Stack이 비어있다면 Event Queue 안의 작업이 Call Stack으로 이동하고 실행됨
Code Examples
function func1() {
console.log('func1');
func2();
}
function func2() {
setTimeout(function () {
console.log('func2');
}, 0);
func3();
}
function func3() {
console.log('func3');
}
func1();
- 위 예제 설명
- 함수 func1이 호출되면, 함수 func1이 Call Stack에 쌓임
- 함수 func1이 함수 func2를 호출하므로, 함수 func2가 Call Stack에 쌓임
- 함수 func2가 호출되면, setTimeout이 호출됨
- setTimeout의 콜백함수는 즉시 실행되지 않고 지정 대기 시간만큼 기다리다가 'tick' 이벤트가 발생하면 Task Queue로 이동함
- Task Queue로 이동한 콜백함수는 Call Stack이 비어졌을 때 Call Stack으로 이동하여 실행됨
- 함수 func2가 함수 func3을 호출하므로, 함수 func3이 Call Stack에 쌓임
- 함수 func3이 호출되고 종료되면, Call Stack에서 함수 func3이 제거됨
- 함수 func2가 종료되면, Call Stack에서 함수 func2이 제거됨
- 함수 func1가 종료되면, Call Stack에서 함수 func1이 제거됨
- Call Stack이 비어졌으므로, setTimeout의 콜백함수가 Call Stack으로 이동하여 실행됨
- DOM 이벤트 핸들러도 setTimeout의 콜백함수와 비슷하게 동작함
function func1() {
console.log('func1');
func2();
}
function func2() {
const elem = document.querySelector('.foo');
elem.addEventListener('click', function () {
this.style.backgroundColor = 'indigo';
console.log('func2');
});
func3();
}
function func3() {
console.log('func3');
}
func1();
- 위 예제 설명
-
함수 func1이 호출되면, 함수 func1은 Call Stack에 쌓임
-
함수 func1이 함수 func2를 호출하므로, 함수 func2가 Call Stack에 쌓임
-
함수 func2가 호출되면, addEventListener가 호출됨
- addEventListener의 콜백함수는 핸들러가 등록된 DOM 요소에서 특정한 이벤트가 발생했을 때 Task Queue로 이동함
- Task Queue로 이동한 addEventListener의 콜백함수는 Call Stack이 비어졌을 때 Call Stack으로 이동하여 실행됨
Task

- setTimeout과 promise를 같이 사용한 코드 예제
setTimeout(function() {
console.log('A');
}, 0);
Promise.resolve().then(function() {
console.log('B');
}).then(function() {
console.log('C');
});
- 위 예제 설명
setTimeout 함수는 콜백함수 A를 Task Queue(Macrotask Queue)에 추가함
then 메서드는 콜백함수 B를 Microtask Queue에 추가함
- Event Loop가 Task Queue 대신 Microtask Queue가 비어있는지 먼저 확인하고, 그 queue에 있는 콜백함수 B를 실행함
then 메서드가 콜백함수 C를 Microtask Queue에 추가함
- Event Loop는 다시 Microtask Queue를 확인하고, 그 queue에 있는 콜백함수 C를 실행함
- Event Look는 Microtask Queue가 비어있음을 확인 한 후 Task Queue에 있는 콜백함수 A를 실행함
Scheduling macrotask & microtask
- 새로운 macrotask를 스케쥴링하는 방법
- 지연시간이 0인
setTimeout(f)를 사용하기
- 이 방법을 사용하면 계산이 복잡한 큰 작업을 여러 작업으로 쪼갤 수 있음
- 이벤트가 완전히 처리되고 난 후(버블링이 끝난 후)에 특정 작업을 수행하도록 스케쥴링할 때도 사용됨
setTimout(f,0)의 '0'
- 브라우저는 내부적으로 타이머의 최소단위(Tick)를 정하여 관리하기 때문에, 실제로는 즉시가 아니라 그 최소단위만큼 지난 후에 태스크 큐에 추가되게 됨
- 이 최소단위는 브라우저별로 조금씩 다름
- 예) 크롬 브라우저의 경우, 최소단위로 4ms 사용
- 새로운 microtask를 스케쥴링하는 방법
queueMicrotask(f) 사용하기;
promise의 .then/catch/finally 핸들러 사용하기
참고