JavaScript 비동기를 공부하다 보면 결국 event loop를 만나게 됩니다. Promise, setTimeout, async/await를 어느 정도 써 봤더라도 “왜 어떤 코드는 바로 실행되고, 어떤 코드는 나중에 실행되지?”라는 질문은 계속 남기 쉽습니다.
이때 event loop 개념을 잡으면 JavaScript가 왜 그렇게 동작하는지 훨씬 덜 신기하고 더 예측 가능하게 보이기 시작합니다. 특히 “JavaScript는 한 번에 하나씩 실행된다면서, 비동기는 도대체 어떻게 가능한가?”라는 질문에 답을 주는 핵심 개념이 바로 여기 있습니다.
이 글에서는 아래 내용을 정리합니다.
- event loop가 무엇인지
- call stack, queue, microtask가 어떤 역할을 하는지
- Promise와
setTimeout이 왜 다른 순서로 보이는지
핵심은 event loop는 비동기 작업 그 자체를 수행하는 엔진이라기보다, “실행할 준비가 된 작업”을 적절한 시점에 call stack으로 올려 주는 조정 메커니즘이라는 점입니다.
Event Loop를 왜 알아야 할까
겉보기에는 JavaScript 코드가 위에서 아래로 한 줄씩 실행되는 것처럼 보입니다. 그런데 실제 앱에서는 아래 같은 일이 계속 함께 일어납니다.
- 버튼 클릭 이벤트 처리
- 네트워크 응답 처리
setTimeout콜백 실행- Promise 후속 처리
async/await이어서 실행
이 동작 순서를 이해하지 못하면, 비동기 코드를 디버깅할 때 “왜 이 로그가 먼저 찍히지?”, “왜 UI가 잠깐 멈췄지?” 같은 상황에서 자꾸 막히게 됩니다.
즉 event loop는 단순한 이론이 아니라, 실행 순서와 체감 성능을 읽기 위한 기본기입니다.
먼저 알아둘 세 가지
입문 단계에서는 아래 세 가지만 먼저 잡으면 충분합니다.
- call stack
- task queue
- microtask queue
그리고 이 셋을 계속 확인하는 흐름이 event loop입니다.
1. Call Stack
지금 실행 중인 함수들이 쌓이는 공간입니다. JavaScript는 기본적으로 call stack 맨 위의 작업을 실행합니다.
2. Task Queue
타이머 콜백, DOM 이벤트, 일부 비동기 완료 후속 처리처럼 “나중에 실행할 일”이 기다리는 곳입니다. 흔히 macrotask queue라고 설명되는 영역과 연결됩니다.
3. Microtask Queue
Promise 후속 처리, queueMicrotask, await 이후 이어지는 흐름처럼 더 빠르게 비워지는 후속 작업이 대기하는 곳입니다.
실무 감각으로는 이렇게 이해하면 됩니다.
- 현재 실행 중인 일은 call stack에 있음
- 나중에 할 일은 queue에 대기함
- Promise 계열 후속 처리는 일반 timer보다 먼저 처리되는 경우가 많음
Event Loop는 실제로 무엇을 할까
event loop는 계속 아래를 확인합니다.
- call stack이 비었는가
- microtask queue에 실행할 것이 있는가
- 그다음 task queue에 실행할 것이 있는가
즉 “다음 차례를 누구에게 줄까”를 계속 판단하는 조정자에 가깝습니다.
그래서 event loop를 이해한다는 것은, 비동기 코드의 실행 시점을 대략 예측할 수 있게 된다는 뜻이기도 합니다.
가장 기본적인 예제
console.log('A');
setTimeout(() => {
console.log('B');
}, 0);
console.log('C');
처음 보면 A, B, C를 예상하기 쉽지만 실제로는 보통 이렇게 나옵니다.
A
C
B
이유는 setTimeout 콜백이 바로 call stack에 올라가는 것이 아니기 때문입니다. 타이머 대기가 끝난 뒤 실행 가능한 상태가 되면 queue 쪽으로 가고, 현재 stack 작업이 끝난 다음에야 event loop를 통해 실행됩니다.
즉 setTimeout(fn, 0)는 “즉시 실행”이 아니라 **“현재 stack이 끝난 뒤 가능한 한 빨리 실행”**에 더 가깝습니다.
Promise는 왜 더 먼저 실행돼 보일까
입문자들이 가장 자주 헷갈리는 부분입니다.
console.log('start');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');
보통 결과는 이렇게 나옵니다.
start
end
promise
timeout
왜 그럴까요? Promise의 후속 처리(then, await 이후 이어지는 코드)는 보통 microtask로 다뤄지고, timer 콜백보다 먼저 비워지는 흐름을 따르기 때문입니다.
입문 단계에서는 아래 정도로 이해해도 충분합니다.
- 현재 stack 작업이 끝난다
- microtask queue를 먼저 비운다
- 그다음 task queue의 작업을 처리한다
그래서 Promise 후속 처리가 timer보다 먼저 보이는 경우가 많습니다.
async/await 와 event loop의 관계
async/await를 쓰면 코드가 순차적으로 읽혀서 동기 코드처럼 보입니다. 하지만 내부적으로는 Promise 기반 비동기 흐름 위에서 동작합니다.
async function run() {
console.log('1');
await Promise.resolve();
console.log('2');
}
console.log('start');
run();
console.log('end');
이 코드는 보통 아래처럼 보입니다.
start
1
end
2
즉 await 이후의 코드는 “지금 즉시 같은 stack에서 계속 실행”되는 것이 아니라, Promise 후속 흐름으로 이어집니다.
그래서 async/await는 읽기 쉬운 문법일 뿐, event loop와 무관한 별도 세계가 아닙니다.
브라우저가 잠깐 멈추는 이유도 여기와 연결된다
많은 사람이 event loop를 “비동기 실행 순서” 정도로만 생각하지만, UI 멈춤과도 직접 연결됩니다.
예를 들어 무거운 CPU 작업이 call stack을 오래 점유하면:
- 클릭 이벤트 처리가 늦어지고
- 화면 갱신이 밀리고
- Promise 후속 처리도 늦어지고
- 타이머 콜백도 대기하게 됩니다
즉 event loop가 있다고 해서 모든 것이 마법처럼 부드럽게 도는 것은 아닙니다. 현재 stack을 오래 붙잡는 작업이 있으면 전체 흐름이 답답해질 수 있습니다.
자주 하는 오해
1. setTimeout(fn, 0)이면 바로 실행된다
아닙니다. 현재 stack 작업이 끝난 뒤 실행 가능한 후보가 된다고 이해하는 편이 맞습니다.
2. event loop가 외부 작업을 직접 수행한다
더 정확하게는 실행 시점을 조정하고, 준비된 작업을 stack에 올리는 흐름으로 이해하는 것이 좋습니다.
3. async/await를 쓰면 event loop를 몰라도 된다
문법은 쉬워지지만, 실행 순서가 헷갈리는 순간 결국 event loop 개념이 필요해집니다.
처음 공부할 때 좋은 연습
console.log와setTimeout조합을 직접 실행해 보기- Promise 예제를 추가해서 순서 비교해 보기
- 같은 흐름을
async/await로 바꿔 보기 - 예상한 순서와 실제 순서를 비교해 보기
직접 결과를 보면 event loop가 문장 설명보다 훨씬 빨리 몸에 들어옵니다.
FAQ
Q. event loop는 브라우저에서만 중요하나요
아닙니다. Node.js 같은 런타임에서도 매우 중요합니다.
Q. Promise 와 setTimeout 의 순서가 왜 다르게 보이나요
microtask와 task 계열의 처리 순서 차이 때문입니다.
Q. event loop를 완벽히 알아야 비동기 코드를 쓸 수 있나요
처음부터 완벽할 필요는 없습니다. 다만 실행 순서가 헷갈릴 때 가장 강력한 설명 도구가 바로 event loop입니다.
Read Next
- 비동기 문법 자체를 다시 정리하고 싶다면 Promise 와 async/await 가이드를 함께 읽어보세요.
- 기다리는 방식의 큰 그림을 먼저 보고 싶다면 동기 vs 비동기 가이드가 잘 이어집니다.
- 여러 작업을 겹쳐 다루는 관점까지 연결하고 싶다면 Concurrency vs Parallelism 가이드도 함께 보면 좋습니다.
먼저 읽어볼 가이드
검색 유입이 많은 핵심 글부터 이어서 보세요.
- 미들웨어 트러블슈팅 가이드: Redis, RabbitMQ, Kafka 중 어디부터 볼까 Redis, RabbitMQ, Kafka가 함께 있는 시스템에서 지금 보이는 장애가 어느 계층에 더 가까운지, 첫 10분 안에 무엇을 확인하고 어떤 글로 들어가야 하는지 정리한 실전 허브 가이드입니다.
- Kubernetes CrashLoopBackOff: 먼저 볼 것들 startup failure, probe, config, resource limit 관점에서 CrashLoopBackOff를 어떻게 나눠서 봐야 하는지 정리한 가이드입니다.
- Astro 기술 블로그 SEO 체크리스트: 트래픽 기다리기 전에 먼저 고칠 것 Astro 기술 블로그를 위한 실전 SEO 체크리스트입니다. 배포 호스트 확인, robots.txt, sitemap, canonical, hreflang, 구조화 데이터, 페이지별 메타데이터, noindex 판단, 검증 명령까지 우선순위대로 정리합니다.
- 다국어 블로그 canonical과 hreflang 설정 가이드: 무엇을 확인하고 어디서 깨질까 다국어 블로그에서 canonical과 hreflang을 어떻게 설정해야 하는지 실전 기준으로 정리합니다. self-canonical, 상호 연결되는 hreflang 묶음, x-default, 카테고리 페이지, 최종 렌더 HTML 점검, 한 언어 버전이 다른 언어 버전을 눌러버리는 실수까지 다룹니다.
- OpenAI Codex CLI 설치 가이드: 설치, 인증, 첫 작업까지 OpenAI Codex CLI를 실전 기준으로 설치하는 방법을 정리했다. 설치, 로그인, 첫 실행, Windows 주의점, 첫 작업을 어떻게 시작하면 좋은지까지 다룬다.