IT & 웹개발

JavaScript 이벤트 루프(Event Loop)와 비동기 처리 원리

kkwako 2025. 4. 2. 17:36

1. 개요

JavaScript는 싱글 스레드(single-thread) 기반 언어로, 하나의 실행 컨텍스트에서 코드가 순차적으로 실행된다. 하지만 **비동기 작업(Asynchronous Task)**을 효율적으로 처리하기 위해 **이벤트 루프(Event Loop)**라는 메커니즘을 사용한다.

이벤트 루프는 비동기 코드(예: setTimeout, fetch, Promise 등)가 실행될 때, 어떻게 처리되는지 결정하는 핵심 개념이다. 본 글에서는 이벤트 루프의 원리와 함께 **비동기 처리 방식(콜백, 프로미스, async/await)**에 대해 알아본다.


2. JavaScript의 실행 방식

(1) 싱글 스레드 모델

JavaScript는 기본적으로 싱글 스레드 환경에서 동작하며, 한 번에 한 가지 작업만 실행할 수 있다.

javascript
복사편집
console.log("Start"); // (1) setTimeout(() => console.log("Timeout"), 1000); // (3) console.log("End"); // (2)

출력 결과는 다음과 같다.

sql
복사편집
Start End Timeout

이처럼 setTimeout이 지정된 시간 후에 실행되는 이유는 이벤트 루프와 태스크 큐(Task Queue)가 존재하기 때문이다.

(2) 실행 컨텍스트(Execution Context)와 콜 스택(Call Stack)

JavaScript 코드는 실행될 때 **실행 컨텍스트(Execution Context)**가 생성되며, 이 컨텍스트는 **콜 스택(Call Stack)**에 저장된다.

javascript
복사편집
function first() { console.log("First"); } function second() { console.log("Second"); first(); } second();

실행 과정:

  1. second()가 호출되면 second 실행 컨텍스트가 콜 스택에 추가됨
  2. console.log("Second") 실행 후, first() 호출
  3. first 실행 컨텍스트가 콜 스택에 추가됨
  4. console.log("First") 실행 후 first 컨텍스트 제거
  5. second 실행 컨텍스트도 제거

출력 결과:

sql
복사편집
Second First

이처럼 JavaScript는 콜 스택을 사용하여 동기적으로 코드 실행을 관리한다.


3. 이벤트 루프(Event Loop)란?

이벤트 루프(Event Loop)는 콜 스택과 태스크 큐(Task Queue)를 모니터링하며, 스택이 비면 대기 중인 비동기 작업을 실행하는 역할을 한다.

(1) 이벤트 루프의 동작 원리

  1. 콜 스택이 비어 있는지 확인
  2. 태스크 큐에서 대기 중인 작업이 있는지 확인
  3. 콜 스택이 비어 있으면 태스크 큐에서 작업을 꺼내 실행
javascript
복사편집
console.log("Start"); setTimeout(() => { console.log("Timeout"); }, 0); console.log("End");

출력 결과:

sql
복사편집
Start End Timeout

(2) 태스크 큐(Task Queue)와 마이크로태스크 큐(Microtask Queue)

비동기 작업은 태스크 큐(Task Queue) 또는 **마이크로태스크 큐(Microtask Queue)**에 추가된다.

  • 태스크 큐(Task Queue): setTimeout, setInterval, fetch 응답 등이 여기에 저장됨
  • 마이크로태스크 큐(Microtask Queue): Promise.then(), queueMicrotask() 등이 여기에 저장됨

마이크로태스크가 항상 우선 실행된다.

javascript
복사편집
console.log("Start"); setTimeout(() => { console.log("setTimeout"); }, 0); Promise.resolve().then(() => { console.log("Promise"); }); console.log("End");

출력 결과:

javascript
복사편집
Start End Promise setTimeout

마이크로태스크(Promise.then())가 태스크 큐(setTimeout)보다 먼저 실행된다.


4. 비동기 처리 방법

(1) 콜백(Callback) 함수

콜백은 비동기 작업이 완료된 후 실행되는 함수다.

javascript
복사편집
function fetchData(callback) { setTimeout(() => { callback("Data loaded"); }, 1000); } fetchData((data) => { console.log(data); // "Data loaded" });

콜백 방식은 단순하지만, 콜백 지옥(Callback Hell) 문제를 유발할 수 있다.

javascript
복사편집
fetchData((data) => { processData(data, (result) => { saveData(result, (response) => { console.log(response); }); }); });

콜백 중첩이 깊어지면 코드 가독성이 떨어진다.

(2) 프로미스(Promise) 사용

Promise는 비동기 작업을 보다 구조적으로 처리할 수 있는 객체다.

javascript
복사편집
function fetchData() { return new Promise((resolve) => { setTimeout(() => { resolve("Data loaded"); }, 1000); }); } fetchData().then((data) => { console.log(data); });

프로미스를 사용하면 **체이닝(Promise Chaining)**이 가능하다.

javascript
복사편집
fetchData() .then((data) => processData(data)) .then((result) => saveData(result)) .then((response) => console.log(response)) .catch((error) => console.error(error));

(3) async/await 사용

async/await는 프로미스를 기반으로 동작하는 문법으로, 코드를 동기적으로 작성할 수 있도록 도와준다.

javascript
복사편집
async function loadData() { const data = await fetchData(); console.log(data); } loadData();

✅ async/await를 사용하면 코드를 보다 직관적으로 작성할 수 있다.


JavaScript 이벤트 루프(Event Loop)와 비동기 처리 원리

 

5. 이벤트 루프 최적화 및 활용 전략

(1) 태스크 큐 지연 최소화

비동기 작업이 너무 많으면 이벤트 루프가 지연될 수 있다. 불필요한 타이머(setTimeout) 사용을 줄이는 것이 중요하다.

javascript
복사편집
setTimeout(() => { console.log("Heavy task"); }, 0);

대신, requestAnimationFrame 또는 queueMicrotask를 활용하면 더 빠르게 실행할 수 있다.

javascript
복사편집
queueMicrotask(() => { console.log("Faster execution"); });

(2) 무거운 작업을 Web Worker로 분리

CPU 집약적인 작업은 Web Worker를 사용하여 이벤트 루프를 차단하지 않도록 한다.

javascript
복사편집
const worker = new Worker("worker.js"); worker.postMessage("Start");

(3) await 남용 방지

await를 남발하면 코드 실행이 차례로 진행되므로, 병렬 실행이 가능할 경우 Promise.all()을 활용한다.

javascript
복사편집
const [data1, data2] = await Promise.all([fetchData1(), fetchData2()]);

6. 결론

JavaScript의 이벤트 루프는 비동기 작업을 효율적으로 처리하는 핵심 메커니즘이다.

핵심 요약

  • JavaScript는 싱글 스레드지만 이벤트 루프를 통해 비동기 처리 가능
  • 비동기 작업은 **태스크 큐(Task Queue) 또는 마이크로태스크 큐(Microtask Queue)**에서 관리됨
  • setTimeout보다 Promise.then()이 먼저 실행됨
  • async/await을 활용하면 비동기 코드를 보다 가독성 좋게 작성 가능

이벤트 루프를 이해하면 JavaScript의 비동기 실행 원리를 보다 효율적으로 활용할 수 있다.