console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
Promise.resolve().then(() => setTimeout(() => console.log(4)));
Promise.resolve().then(() => console.log(5));
setTimeout(() => console.log(6));
console.log(7);
서론
JavaScript의 이벤트 루프는 비동기 작업을 관리하며 프로그램 실행의 중심 역할을 한다. JavaScript 코드가 실행되면 객체와 기본형 변수는 메모리의 힙과 메모리 스택에 로드된다. 이후 함수 호출과 비동기 작업은 콜 스택, 콜백 큐, 마이크로태스크 큐, 매크로태스크 큐에 배치된다. 이벤트 루프는 이들 간의 우선순위를 조정하며 실행한다. 이번 글에서는 이러한 개념과 동작 원리를 살펴본다.
주요 개념
Call stack
Call stack은 자바스크립트 엔진이 현재 실행 중인 함수와 그 함수들이 호출한 다른 함수들을 추적하는 자료 구조다. 이는 LIFO(Last In, First Out) 원칙에 따라 동작한다. 함수가 호출되면 Call stack의 맨 위에 push되고, 함수의 실행이 종료되면 Call stack에서 pop된다.
Callback queue
Callback queue는 비동기 작업의 Callback 함수들이 대기하는 곳이다. 비동기 작업이 스택에서 pop되면 해당 Callback 함수가 콜백 큐에 추가된다. Callback queue는 FIFO(First In, First Out) 원칙에 따라 동작한다. 즉, 먼저 들어온 Callback 함수가 먼저 실행된다.
Microtask queue
마이크로태스크 큐는 Promise와 queueMicrotask로 생성된 태스크가 대기하는 큐다. 마이크로태스크는 매크로태스크보다 높은 우선순위를 가진다.
Macrotask queue
매크로태스크 큐는 모든 비동기 태스크가 기본적으로 대기하는 큐다. setTimeout, setInterval, I/O 작업 등이 포함되며 마이크로태스크 실행 후 처리된다.
이벤트 루프 동작방식
이벤트 루프는 콜 스택과 대기 중인 태스크 큐를 관리한다. 콜 스택이 비어 있으면, 이벤트 루프는 실행 가능한 작업을 찾는다. 먼저 마이크로태스크 큐에 있는 모든 작업을 실행한 뒤 매크로태스크 큐에서 태스크를 처리한다. 태스크 실행이 끝날 때마다 DOM 렌더링이 발생한다.
예를 들어 다음 코드를 보자.
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
Promise.resolve().then(() => setTimeout(() => console.log(4)));
Promise.resolve().then(() => console.log(5));
setTimeout(() => console.log(6));
console.log(7);
위 코드를 실행하면 다음과 같은 순서로 출력된다.
1, 7, 3, 5, 2, 6, 4.
코드의 실행 과정은 다음과 같다. 먼저 console.log(1)과 console.log(7)은 동기 코드로 즉시 실행된다. setTimeout은 매크로태스크 큐에 등록되고, Promise.then은 마이크로태스크 큐에 등록된다. 마이크로태스크 큐의 태스크들은 모두 실행된 뒤 매크로태스크 큐의 작업들이 순차적으로 실행된다.
Microtask Queue와 Macrotask Queue 활용 방법
긴 작업을 효율적으로 처리하려면 CPU-집약적 작업을 나누어 실행하는 것이 중요하다. 예를 들어 긴 계산 작업을 setTimeout으로 나누면 UI가 멈추는 것을 방지할 수 있다. 다음은 비동기로 작업을 분리하여 CPU를 최적화하는 코드다.
let i = 0;
function count() {
do {
i++;
} while (i % 1e6 != 0);
if (i < 1e9) {
setTimeout(count);
}
}
count();
이와 같이 DOM 업데이트를 분리하여 작업하면 진행 상태를 표시할 수 있다. 이를 통해 작업의 진행 상황을 사용자에게 보여주는 코드도 구현할 수 있다.
let i = 0;
function count() {
do {
i++;
progress.innerHTML = i;
} while (i % 1e3 != 0);
if (i < 1e7) {
setTimeout(count);
}
}
count();
결론
JavaScript 이벤트 루프는 비동기 작업을 처리하며, 마이크로태스크와 매크로태스크의 우선순위를 기반으로 효율적으로 동작한다. 이를 이해하면 UI 렌더링 최적화, 긴 작업의 분리, 적절한 비동기 코드 설계가 가능하다.
참고자료
'JS' 카테고리의 다른 글
[JS] Promise와 Async/Await의 이해 (0) | 2024.08.23 |
---|---|
[JS] Javascript에서 동기함수와 비동기함수의 차이 (0) | 2024.07.25 |
[JS] Javascript에서의 FP (0) | 2024.07.24 |
[JS] Javascript에서의 모듈 시스템 (1) | 2024.07.23 |
[JS] Javascript에서의 OOP (0) | 2024.07.22 |