JavaScript
이벤트 루프: 코드는 어떤 순서로 실행될까요?
현재 코드를 끝낸 뒤 예약된 함수를 실행합니다. 예약했다고 다른 스레드에서 계산하는 것은 아닙니다.
이럴 때 필요해요
버튼을 눌렀는데 화면이 멈춘다면, 지금 실행 중인 코드가 너무 오래 걸리는지부터 살펴볼 수 있습니다.
어떻게 동작하나요?
지금 실행 중인 일부터 끝내기
브라우저의 메인 스레드는 JavaScript 실행과 화면 변경 같은 일을 담당합니다. 버튼 함수가 실행되는 동안 그 안의 코드는 끝까지 이어집니다. 긴 정렬을 실행하면 그동안 다른 버튼의 처리도 기다릴 수 있습니다.
B를 나중에 출력하도록 예약하기
나중에 실행할 함수를 예약할 수도 있습니다. 아래 예제는 A를 출력하고 B를 출력할 함수를 예약한 뒤 C를 출력합니다. 결과가 준비된 Promise의 then에 맡긴 함수는 현재 코드가 끝난 뒤 실행되므로 출력은 A, C, B입니다. 이런 예약을 처리하는 방식 중 하나가 마이크로태스크(Microtask)입니다.
코드 예제
동작을 설명하는 읽기 전용 예제입니다.
console.log('A');
Promise.resolve().then(() => {
console.log('B');
});
console.log('C');
// 출력: A, C, B현재 실행의 A와 C가 먼저이고 예약한 B가 나중입니다. 출력 순서를 설명하는 예제이며 화면을 그리는 시점이나 입력 지연을 측정하지는 않습니다.
이 개념으로 실습하기
심화 원리와 적용 범위
예약된 일을 처리하는 순서
브라우저는 실행할 일을 태스크라는 단위로 처리하고, 정해진 시점에 마이크로태스크를 처리합니다. 이를 마이크로태스크 체크포인트라고 합니다. 처리 중 추가된 마이크로태스크도 이어서 처리하므로, 계속 예약하거나 긴 계산을 넣으면 입력과 화면 그리기가 기다릴 수 있습니다. Promise로 감쌌다는 이유만으로 화면에 실행 기회를 주지는 않습니다.
타이머와 큰 계산으로 확장하기
setTimeout의 시간은 그 시간이 지나자마자 실행된다는 보장이 아닙니다. requestAnimationFrame은 다음 화면 갱신 기회에 맞춰 호출하는 함수이며 고정 주기 타이머가 아닙니다. 큰 계산을 다른 실행 환경으로 옮기는 Web Worker는 메시지 전달과 시작 비용이 있고 DOM을 직접 바꾸지 못합니다. 짧게 나눠 실행할지 Worker를 쓸지는 실제 작업을 비교해 결정합니다.
자주 헷갈리는 포인트
- 비동기 실행은 작업 완료 시점을 조율하는 방식이며 멀티스레드 병렬 실행과 같은 뜻이 아닙니다.
- await Promise.resolve()를 반복하는 것은 마이크로태스크만 이어 붙일 뿐 브라우저 렌더링에 실행 기회를 양보하지 않습니다.
내 말로 설명해 보기
무거운 정렬 계산을 Promise.then 콜백 안에 넣었는데도 화면(UI)이 멈추는 이유는 무엇인가요?
생각할 단서 콜백을 예약한 뒤 그 계산을 실제로 수행하는 실행 환경을 확인하세요.
예시 답변 확인
설명에 들어갈 핵심
- Promise.then에 등록한 콜백도 결국 같은 메인 스레드에서 실행됩니다.
- 마이크로태스크 안에서 긴 계산을 수행하면 사용자 입력과 화면 렌더링 기회가 그만큼 밀립니다.
- 비동기로 함수를 예약했다는 것이 별도 스레드에서의 병렬 실행을 뜻하지는 않습니다.
예시 답변
then은 정렬을 나중에 실행하도록 예약하지만 계산을 다른 스레드로 옮기지 않습니다. 긴 정렬이 실행되는 동안 같은 메인 스레드의 입력 처리가 기다릴 수 있습니다. 단순한 Promise 포장만으로 화면 반응이 보장되지 않습니다.
문장을 그대로 외우기보다 예제의 값과 실행 흐름을 떠올리며 설명해 보세요.
실전 시나리오에 적용해 보기
함께 읽으면 좋은 개념
관련 면접 질문 3개
Blocking과 Non-blocking은 동기·비동기와 어떻게 다른가요?
Blocking과 Non-blocking은 호출된 작업이 끝날 때까지 현재 실행 흐름(제어권)을 멈춰 세우는지 여부를 뜻하고, 동기와 비동기는 작업의 완료 결과와 후속 처리를 호출 순서에 맞춰 바로 이어가는지 아니면 나중에 전달받아 실행하는지를 뜻합니다. 예를 들어 브라우저의 fetch는 기다리는 동안 메인 스레드를 막지 않는 Non-blocking 비동기 API이지만, 응답을 받은 뒤 콜백 안에서 수억 번의 루프 계산을 돌리면 그 시간 동안은 메인 스레드가 Blocking되어 화면과 입력이 멈추게 됩니다.
꼬리질문과 참고자료 →동기와 비동기 실행은 결과 처리 순서가 어떻게 다른가요?
동기 실행은 한 작업이 완전히 끝난 뒤에야 그다음 줄 코드로 넘어가는 순차적인 실행 방식이고, 비동기 실행은 시간이 걸리는 작업을 요청해 둔 뒤 다음 코드를 먼저 진행하고 작업이 완료되면 등록해 둔 콜백이나 Promise를 통해 후속 처리를 이어가는 방식입니다. 브라우저에서는 네트워크 요청이나 타이머 대기는 호스트 환경이 처리하지만, 완료 후 실행되는 JavaScript 콜백들은 이벤트 루프 규칙에 따라 메인 스레드에서 하나씩 순서대로 실행됩니다.
꼬리질문과 참고자료 →이벤트 루프·마이크로태스크·Worker·requestAnimationFrame은 어떻게 다른가요?
이벤트 루프는 호출 스택이 비었을 때 대기 중인 작업을 꺼내 실행하며 브라우저의 메인 스레드 흐름을 조율하는 메커니즘입니다. 타이머나 클릭 이벤트는 일반 태스크(매크로태스크)로 스케줄되지만, Promise.then이나 queueMicrotask는 현재 실행 중인 코드가 끝난 직후의 마이크로태스크 체크포인트에서 큐가 빌 때까지 먼저 실행됩니다. 또한 화면을 그리기 직전의 프레임 타이밍에 맞추려면 requestAnimationFrame을 쓰고, 무거운 CPU 연산을 메인 스레드와 분리해 병렬로 처리하려면 DOM 접근이 없는 Web Worker를 사용합니다.
꼬리질문과 참고자료 →