인프런 커뮤니티 질문&답변
이벤트 루프 실행 관련
해결된 질문
작성
·
72
0
저는 풀스택 주니어입니다.
특수 목적을 위해 바닐라js에서 ,document.innerHTML이나 element.remove(), element.append() 등으로 html element의 생성과 삭제가 빈번한
로직을 다루고 있습니다.
이 때 종종 브라우저 렌더링의 실행 완료에 대해 명확하게 알지 못해서 찝찝한 채로 브라우저에 렌더링 일을 시키고 있습니다.
그러던 중 이벤트 루프 관련 3개 강의(16,17,18강) 를 듣고 복습차, 17강으로 다시 돌아와서 보는 2분18초 경에 브라우저 랜더링에 대해 궁금한게 생겼습니다.
예를 들면
const mySchedule = async () => {
await randeringPipelineTask(); //랜더링파이프라인
await macroTask(); // 매크로태스크();
}으로 구성된 mySchedule 메서드가 있다고 가정하겠습니다.
macroTask()는 renderingPipelineTask()의 브라우저 페인트까지 모두 완료를 확인한 후에 실행될까요? 아니면 JS엔진이라는 것은, 페인트는 브라우저에 렌더링을 해달라고 부탁까지만하고 macroTask()를 실행할까요?
('실행의 시작'과 '실행의 끝'을 나눠서 생각해서 그런지 생각하면 할수록 햇갈립니다.)
답변 2
0
안녕하세요 동군님.
단순히 배우는 것을 넘어 실제 시스템에서 심도 깊은 이해를 위해 고민을 많이 해보신 거 같네요. 정말 훌륭한 자세라 생각합니다.
질문에 대해 결론부터 말씀드리면 macroTask()는 randeringPipelineTask()가 요청한 브라우저의 페인트(Paint) 작업이 완전히 완료된 후에 실행될 가능성이 매우 높습니다.
이유는 자바스크립트의 이벤트 루프의 명확한 작동 순서 덕분입니다.
질문하신 mySchedule 메서드 시나리오를 이벤트 루프 주기 관점에서 분석해 보겠습니다.
const mySchedule = async () => {
await randeringPipelineTask(); // ① 랜더링파이프라인 (≒ 한 프레임의 작업)
await macroTask(); // ② 매크로 태스크
}randeringPipelineTask()가 완료되고 Promise가 resolve될 때까지 JS 엔진은 대기합니다.이 대기 시간 사이에 브라우저는 렌더링 업데이트를 수행합니다.
randeringPipelineTask()가 resolve되고, 그 콜백(macroTask()로 이동하는 로직)이 Microtask Queue로 들어갑니다.이 Microtask Queue가 비워진 후, 브라우저는 렌더링 파이프라인(Layout, Paint)을 수행합니다.
렌더링이 완료된 후, 이벤트 루프는 다음 Macrotask를 큐에서 꺼내 콜스택으로 넣습니다.
핵심은 이벤트 루프는 매 주기마다 반드시 렌더링을 처리한 후에 다음 매크로태스크를 처리한다는 점입니다. 따라서 macroTask()가 다음 주기(cycle)의 Macrotask로 실행된다면, 그 전에 발생해야 할 렌더링 작업은 모두 완료됩니다.
수강생님의 혼란은 "JS 엔진은 부탁까지만 하고 다음을 실행할까?"라는 부분에서 기인합니다. 일반적인 DOM 조작은 '부탁까지만' 하는 것이 맞습니다.
예를 들어, element.remove()를 수백 번 호출해도, 실제 레이아웃 계산과 페인트는 콜스택이 완전히 비워진 후(혹은 requestAnimationFrame 같은 특정 지점)에 단 한 번의 렌더링 스텝에서 배치(Batch)로 처리됩니다.
그래서 이벤트 루프의 표준 주기를 살펴보면
JS 엔진: 현재 MacroTask 실행
JS 엔진: MicroTask Queue 비우기
브라우저: Update the rendering 과정으로 Layout, Paint, Composite 실행
이벤트 루프: 다음 MacroTask 선택
렌더링은 마이크로태스크가 비워진 후에 이뤄지기 때문에 구체적인 함수에 대해 코드가 있지는 않지만 randeringPipelineTask 함수를 추정해본다면 다음과 같습니다.
만약 randeringPipelineTask()가 다음 렌더링 완료를 기다리는 역할을 하려면, 가장 표준적이고 강력한 방법은 requestAnimationFrame (RAF)과 Promise를 조합하는 것입니다.
randeringPipelineTask()내부에서 DOM을 변경하고, RAF를 요청합니다.RAF 콜백은 렌더링 바로 직전에 실행됩니다.
RAF 콜백 내에서
setTimeout(0)이나 다른 Macrotask를 이용하거나, 혹은 Microtask를 한 번 더 사용하여 Promise를 resolve 시켜 다음 프레임 경계를 넘기는 방식으로 페인트 완료 후에야 비로소randeringPipelineTask()의await가 해제되도록 구성해야 합니다.
결론적으로, await가 걸려있다는 것은 브라우저의 한 프레임(주기) 경계를 넘기도록 설계된 비동기 로직을 사용했다는 의미이며, 이벤트 루프의 설계상 이 경계를 넘으면 렌더링은 이미 완료된 상태입니다.
이러한 이유로 DOM 조작을 빈번하게 해야하는 경우라면, 매우 특수한 경우가 되겠지만!
requestAnimationFrame(RAF)
MutationObserver: 강의에서는 설명하지 않음
setTimeout(0)
등을 통해 렌더링 완료를 명확히 확인하실 수 있을 겁니다.
답변이 되셨길 바랍니다.
감사합니다.
0
안녕하세요, 인프런 AI 인턴입니다. 동군님.
질문하신 내용은 JavaScript의 이벤트 루프와 브라우저 렌더링 과정에서의 작업 순서에 대한 이해를 돕는 핵심적인 부분입니다. JavaScript 엔진과 브라우저가 어떻게 작업을 스케줄링하고 처리하는지를 이해하는 것이 코드 성능 최적화에 매우 중요합니다.
먼저 JavaScript의 이벤트 루프(event loop)는 태스크 큐에서 태스크를 하나씩 가져와 실행하는 구조입니다. 이 때, 브라우저 렌더링도 중요한 태스크 중 하나로 처리됩니다.
주요 개념 요약
Rendering Pipeline Task: JavaScript는 브라우저에게 DOM을 업데이트하도록 요청하고, 브라우저는 태스크가 끝난 후에 렌더링을 처리합니다. 이 과정에는 페인트(paint) 작업도 포함됩니다.
Macro Task:
setTimeout,setInterval,requestAnimationFrame과 관련된 태스크들이 있습니다. 각 cycle 동안 하나의 macro task가 처리되고, 이 후에 micro task들이 실행됩니다.페인트 작업: 브라우저는 JavaScript가 처리하고 있는 현재 상태 변화(예: DOM 변경)을 이용하여 새로운 화면을 그리기 위한 페인트 작업을 수행합니다. 이 단계는 JavaScript 실행과 비슷한 우선 순위를 가지지 않지만, 이벤트 루프 사이클 사이에서 발생합니다.
따라서 코드의 await renderingPipelineTask(); 부분은 브라우저에 페인트 요청을 보내는 작업일 수 있지만, 실제 렌더링은 이후 브라우저의 렌더링 엔진에 처리됩니다. 이후 await macroTask();를 실행할 때는 이미 태스크 큐의 다음 작업을 수행하는 것입니다. 따라서, macroTask는 renderingPipelineTask의 페인트가 완료된 후에 실행되지는 않을 수 있습니다. 브라우저 렌더링은 비동기적이며, JavaScript 엔진과 비동기적으로 동작합니다.
관련된 추가 설명을 제공한 실행 컨텍스트 깊이 파헤치기 질문을 참고하실 수 있습니다. 이 질문에서는 실행 컨텍스트와 관련된 여러 개념을 상세하게 설명하고 있습니다.
이해하는 데 조금 더 도움이 되셨기를 바랍니다. 계속해서 궁금한 점이 있으시면 추가 질문해 주세요!
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.




