브라우저 이벤트 루프: 태스크·마이크로태스크와 화면 갱신
브라우저 이벤트 루프는 태스크 실행, 마이크로태스크 체크포인트, 렌더링 기회를 조정합니다. Promise 작업이 태스크와 어떻게 다른지와 마이크로태스크가 화면 갱신을 늦출 수 있는 이유를 살펴봅니다.
발행·오류 제보: infostoa · 편집 기준
이 글의 개별 작성자와 전문 검토자 정보는 제공되지 않습니다.
버튼을 눌렀는데 화면이 늦게 바뀌거나, Promise.then() 콜백이 setTimeout()보다 먼저 실행되는 이유는 브라우저가 JavaScript 작업과 화면 갱신을 조정하기 때문입니다. 이벤트 루프에는 태스크 큐와 별도의 마이크로태스크 큐가 있습니다. 둘을 단순히 같은 FIFO 목록으로 이해하면 실행 순서와 렌더링 시점을 잘못 예측하게 됩니다.
1. JavaScript 실행과 브라우저 이벤트 루프
한 JavaScript 실행 환경은 현재 실행 중인 작업을 처리하고, 브라우저는 사용자 입력·타이머·네트워크 완료 같은 일을 나중에 처리할 작업으로 예약합니다. HTML 표준은 이벤트 루프, 하나 이상의 태스크 큐, 별도의 마이크로태스크 큐, 그리고 렌더링 단계를 구분합니다. 이 모델은 브라우저의 실제 스케줄러가 항상 단순한 단일 큐라는 뜻은 아닙니다. 태스크 종류마다 큐와 선택 규칙이 다를 수 있습니다.
일반적인 코드가 동기적으로 실행되는 동안 브라우저는 그 실행이 끝나기를 기다립니다. 긴 계산이나 반복문이 메인 스레드를 계속 점유하면 입력 처리와 렌더링이 늦어질 수 있습니다. 비동기 API는 이후 실행할 콜백을 예약해 현재 호출 스택을 비우지만, 비동기라고 해서 작업이 별도 스레드에서 실행되거나 화면이 즉시 갱신된다는 뜻은 아닙니다.
2. 태스크와 마이크로태스크의 차이
이벤트나 타이머 콜백은 태스크로 실행되는 경우가 많고, Promise 반응 작업과 queueMicrotask() 콜백은 마이크로태스크로 처리됩니다. 정확한 태스크 큐 배치는 API와 사양에 따르므로 “모든 비동기 콜백은 같은 순서”라고 가정하지 마세요. 예를 들어 현재 동기 코드가 끝난 뒤 예약된 Promise 콜백은 보통 setTimeout(..., 0) 콜백보다 먼저 처리되지만, 타이머의 0은 즉시 실행 시각을 보장하는 값이 아닙니다.
HTML 이벤트 루프는 조건에 따라 마이크로태스크 체크포인트를 수행합니다. 체크포인트에서는 큐에 있는 마이크로태스크를 실행하며, 실행 중 새 마이크로태스크가 추가되면 계속 처리될 수 있습니다. 따라서 마이크로태스크가 끝없이 추가되면 다음 태스크 선택이나 렌더링 기회가 뒤로 밀릴 수 있습니다. queueMicrotask()를 반복 호출해 큰 작업을 쪼갠다고 해서 브라우저가 중간에 반드시 화면을 그리는 것은 아닙니다.
3. Promise 완료와 화면에 그려지는 시점
Promise가 resolve되면 그 반응 작업이 마이크로태스크로 예약됩니다. 이미 실행 중인 동기 코드는 먼저 끝나야 하며, 이후 체크포인트에서 마이크로태스크가 처리됩니다. 이 순서는 여러 Promise 콜백이 이어지는 작업을 현재 실행 흐름과 조정하지만, 콜백이 끝난 즉시 브라우저가 화면을 그린다는 보장은 하지 않습니다. 렌더링은 이벤트 루프의 별도 단계이며, 브라우저는 매 태스크 뒤에 반드시 그리는 것이 아니라 렌더링 기회와 문서 상태를 고려합니다.
현재 HTML 표준의 렌더링 단계에서는 렌더링 기회가 있을 때 requestAnimationFrame() 콜백을 실행한 다음 스타일·레이아웃을 갱신하고 그립니다. 브라우저는 렌더링을 건너뛸 수 있으므로 이 콜백은 동기식 새로고침이나 즉시 페인트 명령이 아닙니다. 이는 규격의 단계 순서를 단순화한 설명이며, 모든 태스크 뒤에 프레임이 온다는 뜻은 아닙니다.
애니메이션 프레임에 맞춰 시각적 업데이트를 예약하려면 requestAnimationFrame() 같은 API가 용도에 맞을 수 있습니다. 대량의 계산을 나눠 사용자 입력에 응답하려면 다음 태스크로 양보하거나 작업자 스레드를 검토할 수 있습니다. 선택은 무엇을 보장하려는지에 달려 있습니다. Promise는 비동기 결과 연결에 적합하지만, 화면 프레임을 양보하는 용도로 설계된 스케줄러는 아닙니다.
4. 실무자 관점: 순서와 응답성을 따로 측정
디버깅할 때 먼저 동기 코드가 언제 끝나는지 확인한 뒤, 태스크와 마이크로태스크의 등록 순서를 기록합니다. DevTools의 performance trace에서 긴 작업과 렌더링 시점을 함께 살펴보면 “콜백은 실행됐지만 화면은 아직 바뀌지 않은” 상황을 구분하기 쉽습니다. 타이머 지연을 정확한 시각 보장으로 보거나, 마이크로태스크가 한 번만 실행된다고 가정하지 마세요.
메인 스레드에서 긴 동기 작업을 반복한다면 Promise로 감싸는 것만으로 반응성이 개선되지 않습니다. 실제 작업량을 줄이거나 적절히 나누고, 프레임 일정이 중요한 시각 변화에는 프레임 API를 검토합니다. 이벤트 루프의 세부 선택 순서는 브라우저 사양과 태스크 소스에 좌우되므로, 중요한 동작은 목표 브라우저에서 측정하세요.