Skip to main content

Command Palette

Search for a command to run...

[js] 이벤트 루프와 비동기 처리

Published
•3 min read•View as Markdown
[js] 이벤트 루프와 비동기 처리
D

프론트엔드 개발자 doit입니다.

JavaScript는 싱글 스레드 기반의 언어로, 한 번에 하나의 작업만 실행할 수 있다. 하지만 브라우저 환경에서는 여러 작업이 동시에 이루어지는 것처럼 보이는데, 어떻게 가능할까? 이는 이벤트 루프와 비동기 처리 덕분이다.

1. JavaScript 실행 방식 (콜 스택)

JavaScript 코드는 콜 스택이라는 곳에서 실행된다. 콜 스택은 LIFO (Last In, First Out) 구조로, 가장 나중에 들어온 작업이 먼저 처리된다.

예제 1: 동기 코드 실행

console.log("A");
console.log("B");
console.log("C");

출력 결과:

A
B
C

이처럼 코드가 순차적으로 실행된다.

2. 콜 스택이 블로킹되는 문제

만약 오래 걸리는 작업이 있다면, 콜 스택이 이 작업을 실행하느라 다른 작업을 할 수 없게 된다.

console.log("A");

for (let i = 0; i < 10000000000; i++) {}  // 무거운 연산

console.log("B");

이 경우 브라우저가 멈춘 것처럼 보이는데, 이는 콜 스택이 연산을 수행하느라 다른 작업을 할 수 없기 때문이다.

3. Web API와 비동기 처리

Web API란?

Web API는 브라우저가 제공하는 API 로, JavaScript가 단독으로 처리할 수 없는 기능들을 수행할 수 있도록 도와준다. 즉, 브라우저가 제공하는 기능을 활용해 JavaScript가 네트워크 요청, 타이머 설정, DOM 조작, 이벤트 리스너 등 다양한 작업을 할 수 있게 된다.

Web API의 대표적인 예는 다음과 같다:

  • DOM: HTML 요소를 조작하는 API

  • Fetch API / XMLHttpRequest: 네트워크 요청을 처리하는 API

  • setTimeout / setInterval: 일정 시간이 지난 후 실행되는 타이머 API

  • Geolocation API: 사용자의 위치 정보를 가져오는 API

  • Web Storage: 브라우저에 데이터를 저장하는 API

Web API와 이벤트 루프의 관계

JavaScript는 비동기 함수를 실행할 때 Web API를 활용한다.

예제 2: 비동기 코드 (setTimeout)

console.log("hi");

setTimeout(() => {
    console.log("there");
}, 5000);

console.log("bye");

출력 결과:

hi
bye
there (5초 후 출력)

실행 과정:

  1. console.log("hi") → 콜 스택에서 실행 후 제거

  2. setTimeout() → Web API로 넘겨 처리 (5초 대기 후 실행 예정)

  3. console.log("bye") → 콜 스택에서 실행 후 제거

  4. 5초 후, setTimeout의 콜백이 콜백 큐에 저장됨

  5. 이벤트 루프가 콜 스택이 비었는지 확인 후, 콜백을 가져와 실행

  6. console.log("B") 실행

4. 이벤트 루프(Event Loop)

이벤트 루프는 콜 스택과 콜백 큐를 감시하면서 작업을 관리하는 역할을 한다.

  • 콜 스택이 비어 있을 때만 콜백 큐에서 작업을 가져와 실행한다.

  • 이 덕분에 비동기 작업이 완료된 후에도 프로그램이 멈추지 않고 계속 실행될 수 있다.

5. 마이크로태스크 큐 (Microtask Queue)

비동기 작업에는 콜백 큐 외에도 마이크로태스크 큐가 있다.

console.log("A");

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

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

console.log("D");

출력 결과:

A
D
C
B

실행 과정:

  1. console.log("A") 실행

  2. setTimeout() 실행 → Web API로 넘겨 타이머 시작 (콜백 큐 대기)

  3. Promise.resolve().then() 실행 → 마이크로태스크 큐로 이동

  4. console.log("D") 실행 (동기 코드 실행)

  5. 마이크로태스크 실행 → console.log("C")

  6. 콜백 큐 실행 → console.log("B")

✅ 마이크로태스크(Promise, async/await)가 콜백 큐(setTimeout, setInterval)보다 먼저 실행된다!

6. async/await과 이벤트 루프

async/await 도 결국 Promise 기반의 비동기 코드이다.
await 키워드는 비동기 작업이 끝날 때까지 기다렸다가 실행을 계속하는 역할을 한다.

예제 3: async/await의 실행 순서

console.log("A");

async function fetchData() {
    console.log("B");
    await Promise.resolve();  
    console.log("C");
}

fetchData();

console.log("D");

출력 결과:

A
B
D
C

실행 과정:

  1. console.log("A") 실행

  2. fetchData() 실행 → console.log("B")

  3. await Promise.resolve(); → 마이크로태스크 큐로 이동

  4. console.log("D") 실행 (동기 코드 먼저 실행)

  5. 마이크로태스크 실행 → console.log("C")

7. 정리

✅ JavaScript는 싱글 스레드이지만, Web API와 이벤트 루프를 활용해 비동기 작업을 처리할 수 있다. ✅ 비동기 작업이 완료되면 콜백 큐나 마이크로태스크 큐에 등록되고, 이벤트 루프가 이를 실행한다. ✅ 마이크로태스크(Promise, async/await)가 콜백 큐(setTimeout, setInterval)보다 먼저 실행된다.

💡 결론:

JavaScript는 싱글 스레드이지만 이벤트 루프 덕분에 마치 멀티태스킹처럼 동작한다! 🚀

11 views

More from this blog

Agent Skill로 README 자동화하기

저는 요즘 업무에서 AI 에이전트를 적극적으로 활용하여 반복적인 업무를 최대한 AI로 자동화하기 위해 공부 중인데요. 이번에 했던 작업 중 하나가 README 파일 생성 자동화입니다. AI에게 README를 써달라고 하면 꽤 그럴싸한 결과물이 나옵니다. 하지만 여러 프로젝트에 반복하다 보면 프롬프트가 모호할 때마다 매번 다른 방식으로 문제를 풀기 때문에 들

Mar 31, 20268 min read8

배열을 처리하는 세 가지 방법 (chaining, reduce, for loop)

개발을 하다 보면 배열 데이터를 가공할 일이 정말 많은데요.저는 평소에는 filter, map 같은 메서드 체이닝 방식을 주로 사용합니다. 읽기 쉽고, 데이터 흐름이 단계별로 명확하게 보이기 때문이죠. 하지만 어떠한 계기로(?) 인해 이런 의문을 가지게 되었습니다.. "만약 데이터가 수천만 건으로 늘어난다면? 배열을 여러 번 순회하는 체이닝 방식이 성능 병목을 일으키지는 않을까?" 혹은 "reduce 하나로 합치는 게 베스트일까?" 이 질문을 ...

Feb 4, 20263 min read19

[React] 좋은 추상화란 무엇인가(2) 잘못된 추상화는 중복보다 비싸다

지난 글을 보면 성급했던 재사용 컴포넌트 경험으로 고생을 한 적이 있었는데요.(아직 안 보셨다면? [1편: 좋은 추상화란 무엇인가(1)?] ) 애초에 왜 이런 고생을 했을까를 생각해 보면이유는 단순했습니다. 중복 코드가 거슬리니 바로 추상화를 해버린 것이었죠..똑같은 실수를 반복하지 않기 위해 저만의 기준이 필요하다고 느꼈습니다. 이에 대해 이미 깊이 고민하셨던 Cher Scarlett님과 Kent C. Dodds님의 글을 보면서, 제가 취해야...

Dec 16, 20253 min read25

[React] 좋은 추상화란 무엇인가(1) ? 유연한 컴포넌트 설계를 위한 제어의 역전

재사용을 위해 만든 컴포넌트가, 시간이 지날수록 오히려 재사용하기 어려워진 경험이 있으신가요? 저는 최근 프로젝트에서 SearchFilter 컴포넌트가 그러한데요 프로젝트에는 여러 목록 페이지가 존재했고, 페이지마다 검색 조건이 제각각이었습니다. 그래서 처음에는 Configuration 배열만 넘기면 알아서 그려주는 컴포넌트를 만들었습니다. // ❌ 초기의 설정 기반 접근 (Configuration) const filterFields = [ ...

Dec 9, 202510 min read36

React 시대의 함수형 프로그래밍

바닐라 JS처럼 개발자가 DOM과 상태 변경을 직접 제어하던 시기에는 불변성·순수 함수 같은 함수형 프로그래밍 철학이 지금처럼 중요하지는 않았습니다. 하지만 React(Vue/Redux/SWR/Recoil 등)처럼 선언적·상태 기반 UI가 등장하면서 상태를 “데이터 흐름의 스냅샷”으로 다뤄야 했고, 그때부터 함수형 프로그래밍이 중요해지기 시작했습니다. 🔥 바닐라 JS에서는 왜 FP가 별로 중요하지 않았을까? 바닐라 JS 시대의 UI 개발은...

Dec 8, 20255 min read35
D

Dlog!

39 posts

배우고, 나누며, 함께 성장하는 개발자 🚀