JS 비동기프로그래밍 ( .with 브라우저 )

KB LEE·2025년 2월 16일
0

JavaScript 펼쳐보기

목록 보기
1/5
post-thumbnail

1. 목적

이 문서는 자바스크립트 엔진의 단일 스레드 특성과, 브라우저 환경에서 어떻게 비동기 처리가 이루어지는지 정리하기 위한 문서입니다.

특히 크롬 브라우저를 중심으로 Web API, Event Loop, Callback Queue 등을 활용해 자바스크립트가 비동기 처리를 수행하는 과정을 해석합니다.


2. 자바스크립트 엔진은 단일 스레드로 동작한다

자바스크립트 엔진(V8 등)은 단일 스레드 기반으로 하나의 Call Stack을 사용하여 코드를 순차적으로 처리합니다.

이로 인해 한 번에 하나의 작업만 실행할 수 있지만, 모든 코드를 동기적으로만 실행하면 프로그램의 흐름이 중단되거나 지연되는 문제가 발생할 수 있습니다.
동기식과 비동기식 비교

[출처]: 동기식과 비동기식의 차이

3. 자바스크립트의 비동기 프로그래밍이란?

위 설명대로 엔진 자체는 단일 스레드로 동작하고 동기적으로 코드를 처리합니다.

Q. 그렇다면 자바스크립트는 왜 비동기적으로 작동하나요?

그런데도 “자바스크립트가 비동기로 동작한다”고 표현하는 이유는, 엔진이 직접 처리하지 않는 비동기 작업브라우저Node.js 환경이 대행하기 때문입니다.

  • 브라우저(Web API)Node.js(libuv)에서 타이머, 네트워크 요청, 파일 I/O 등의 비동기 작업을 처리한다.
  • 해당 작업이 완료되면, Event Loop를 통해 콜백 함수가 Call Stack에 다시 전달되어 실행된다.

A.
자바스크립트 엔진은 동기적이지만, 브라우저나 Node.js가 제공하는 별도 시스템과 협력함으로써 비동기 프로그래밍이 가능해집니다.


4. 브라우저 환경에서의 비동기 처리 구조

기본 아키텍처 [출처]: 기본 아키텍처

브라우저(크롬 기준)에서 자바스크립트의 비동기 처리는 다음 요소들이 협력하여 이루어집니다.

  1. Event Loop
    • Call Stack을 감시하고, Callback Queue에 대기 중인 작업을 실행 시점에 맞추어 Call Stack으로 전달
    • 자바스크립트의 실행 순서동시성을 관리하는 핵심 요소입니다.
  2. Web APIs
    • DOM 조작, HTTP 요청, 타이머 등 브라우저가 제공하는 여러 기능을 지칭
    • 엔진이 처리해야 할 비동기 작업을 대행하고, 완료된 시점에 콜백을 큐로 전달
  3. Callback Queue (Task Queue)
    • 비동기 작업의 완료 후 콜백들이 대기하는 큐
    • 일반적으로 Microtask Queue, Animation Frames Queue, Task(Macro) Queue
      1. Microtask Queue (우선순위 가장 높음)
        • 주로 Promise의 후속 처리나 MutationObserver 등이 등록됨.
        • Event Loop는 Microtask Queue에 남아 있는 모든 작업을 우선 처리한 뒤에 다른 큐로 넘어갑니다.
        • Microtask 실행 중 추가로 등록되는 Microtask가 있으면, 해당 작업 역시 우선적으로 처리.
      2. Animation Frames Queue
        • requestAnimationFrame으로 등록된 콜백이 들어가는 큐
        • 브라우저의 렌더링 직전에 실행
        • 보통은 Microtask 실행이 모두 끝난 뒤, 그리고 매크로태스크(MacroTask) 실행 전에 호출됨
          • 하지만 렌더링 타이밍이나 프레임 드롭 상황에 따라 간헐적으로 실제 실행 순서가 달라질 수 있음
      3. Task Queue (MacroTask Queue)
        • setTimeout, setInterval, I/O 이벤트 등 다양한 비동기 작업의 콜백이 등록됨
        • 일반적으로 우선순위가 낮아 Microtask QueueAnimation Frames Queue 다음에 처리
          • 그러나 실제 환경에서는 브라우저 구현 차이, 렌더링 타이밍 등으로 인해 순서가 달라지는 경우가 있습니다.
  4. JS Engine Call Stack
    • 현재 실행 중인 스크립트(함수)를 쌓아두는 스택 구조
    • Event Loop가 Callback Queue에서 작업을 꺼내 스택에 올리면, 자바스크립트 엔진이 이를 순차적으로 실행

Event Loop의 절차

Tick, 이벤트 루프의 한 사이클

  1. 자바스크립트 엔진은 Macrotask 하나를 꺼내서 실행
  2. 실행 과정에서 등록된 Microtask들을 모두 실행
  3. 브라우저인 경우, 그 시점에 렌더링(화면 업데이트) 등을 처리
  4. 이벤트 루프는 다음 틱으로 넘어감

⇒ Macrotask 하나를 꺼내어 실행 → 그 과정에서 생긴 Microtask 처리 → (필요 시) 렌더링 → 다음 틱… 를 반복


5. 코드 예시로 확인하기

5.1 기본 흐름 예시

console.log("스크립트 시작");

setTimeout(() => {
  console.log("MacroTask 실행: setTimeout");
}, 0);

Promise.resolve().then(() => {
  console.log("MicroTask 실행: Promise");
});

console.log("스크립트 종료");
  1. "스크립트 시작"이 즉시 출력
  2. setTimeout() 콜백은 MacroTask Queue에 예약됨 (지연 시간이 0이어도 최소 지연 시간이 적용됩니다).
  3. Promise.resolve().then(...) 콜백은 Microtask Queue에 예약됨
  4. "스크립트 종료"가 출력
  5. 현재 스택이 비면, MicroTask Queue에 있는 콜백이 먼저 실행됨 → "MicroTask 실행: Promise" 출력
  6. 이후 MacroTask Queue 콜백인 "MacroTask 실행: setTimeout"이 실행됨

5.2 MicroTask Queue 우선순위 테스트

console.log("스크립트 시작");

setTimeout(() => {
  console.log("MacroTask 1");
  Promise.resolve().then(() => console.log("MicroTask 2"));
}, 0);

Promise.resolve()
  .then(() => {
    console.log("MicroTask 1");
    Promise.resolve().then(() => console.log("MicroTask 1-1"));
  });

console.log("스크립트 종료");
  1. 동기 코드는 순차적으로 "스크립트 시작", "스크립트 종료"를 출력
  2. setTimeout 콜백은 MacroTask Queue에, Promise 콜백은 MicroTask Queue에 예약됨
  3. 현재 스택이 비어질 때, MicroTask Queue인 "MicroTask 1"이 실행됨
    • 그 내부에서 새롭게 "MicroTask 1-1"이 예약되므로 즉시 다시 실행됨
  4. 이후 MacroTask Queue의 "MacroTask 1"이 실행되고, 그 내부에서 생성된 MicroTask "MicroTask 2"가 순차적으로 실행됨

5.3 Animation Frames Queue vs MacroTask Queue

console.log("스크립트 시작");

requestAnimationFrame(() => {
  console.log("Animation Frames");
});

setTimeout(() => {
  console.log("MacroTask 1");
}, 0);

Promise.resolve().then(() => {
  console.log("MicroTask 1");
});

console.log("스크립트 종료");
  1. "스크립트 시작", "스크립트 종료"가 동기적으로 실행됨
  2. Promise 콜백이 MicroTask Queue에 등록되어 "MicroTask 1"이 먼저 실행됨.
  3. 그 후 렌더링 직전 시점에 "Animation Frames" 콜백이 실행됨.
  4. 마지막으로 MacroTask Queue인 "MacroTask 1"이 실행됨.

이론적으로는 이 순서가 일반적이지만, 렌더링 타이밍, 프레임 드롭, 브라우저 구현 차이 등에 의해 간헐적으로 실제 순서가 달라질 수 있음


6. 정리 및 후기

이상으로 자바스크립트의 단일 스레드 특성과 브라우저의 Web API, Event Loop, Callback Queue를 이용한 비동기 처리 과정을 살펴보았습니다.

프론트엔드 개발자 입장에서 비동기 처리 원리를 정확히 이해하는 것은, 화면과 사용자 경험(UX) 전반에 큰 영향을 미칩니다.

  • 자바스크립트는 단일 스레드로 동작하기 때문에, 긴 시간 소요되는 작업이 Call Stack을 독점하면 UI가 멈춘 것처럼 느껴집니다.
  • 비동기 처리와 이벤트 루프의 흐름을 이해하면, 무거운 연산을 Web Worker타이머 등을 활용해 분산시켜 사용자 인터페이스가 부드럽게 유지되도록 설계할 수 있습니다.
  • Microtask와 MacroTask의 우선순위, requestAnimationFrame 등의 메커니즘을 잘 이해하면, 불필요한 연산이나 리렌더링을 줄이고, 효과적으로 일을 분배할 수 있습니다.
  • 예를 들어 requestAnimationFrame을 활용해 애니메이션을 렌더링 시점에 맞춰 업데이트하면, 리플로우나 리페인트에 대한 오버헤드를 줄일 수 있습니다.

대수롭지 않게 생각할 수 있지만 이슈에 대한 개선사항을 적용해야하는 경우가 있을 때, 위에서 설명했던 특성들을 이해하고 있어야 더 효율적으로 앱을 설계하고 개선할 수 있게 됩니다.

저도 해당 내용을 학습하다보니, Worker에 대한 내용도 궁금하여 학습하게 될 것 같습니다. 🙂

다음에도 더 알차고 깔끔한 내용으로 찾아뵙겠습니다.

읽어주셔서 감사합니다!


7. 참고자료

profile
한 발 더 나아가자

0개의 댓글