FrontEnd Develop

비동기처리의 이해 : Promise, async/await 그리고 MicroTask?

Frisbeen 2025. 5. 19. 19:06

자바스크립트는 단일 스레드 기반 언어지만, 웹 API와 이벤트 루프를 통해 비동기 처리를 지원합니다.

이 글에서는 Promise.then(), async/await, setTimeout() 등이 함께 혼재될 때의 실행 순서와 내부 구조를 이해하기 쉽게 정리합니다.


1. JavaScript의 실행 환경 : 이벤트 루프 구조

자바스크립트는 다음과 같은 실행 큐를 기반으로 동작합니다:

  • Call Stack: 현재 실행 중인 코드
  • Microtask Queue: 우선순위 높은 비동기 작업 → Promise.then(), await, queueMicrotask() 
  • Macrotask Queue: 일반적인 비동기 → setTimeout(), setInterval() 

실행 순서:

  1. 동기 코드 (call stack) 먼저 실행
  2. Microtask Queue의 작업 모두 소진
  3. Macrotask Queue의 작업 하나 실행
  4. 다시 1번부터 반복

2. Promise와 async/await의 개념 정리

Promise

  • 비동기 작업의 상태(state)를 표현하는 객체
  • .then(), .catch(), .finally()를 통해 후속 작업을 예약
  • Microtask Queue에 후속 작업을 등록
Promise.resolve().then(() => console.log('then 실행'));

async/await

  • async 함수는 항상 Promise를 반환
  • await은 해당 Promise가 resolve될 때까지 함수를 일시 중단(pause)
  • 중단 이후 코드는 Microtask로 실행됨
async function fetchData() {
  console.log('시작');
  const res = await fetch('/api');
  console.log('끝');
}

3. 실제 혼재 예제 분석

비동기 우선순위가 높은 Promise , Async(어차피 Promise) 이니 둘 다 MicroTask Queue에 들어갑니다.
그에 따라, setTimeout 보다 먼저 3. Promise then, 그리고 5. await 이후에 2.setTimeout이 실행되는 겁니다.

동기와 비동기가 혼재되어있을때, 우선순위를 기반으로 비동기들의 동기성이 생긴다는 것입니다.

console.log('1. 동기');

setTimeout(() => {
  console.log('2. setTimeout');
}, 0);

Promise.resolve().then(() => {
  console.log('3. Promise then');
});

(async function () {
  console.log('4. async 함수 시작');
  await null;
  console.log('5. await 이후');
})();

console.log('6. 동기 끝');

예상 실행 순서

1. 동기
4. async 함수 시작
6. 동기 끝
3. Promise then
5. await 이후
2. setTimeout

설명:

  • console.log('1'), 4, 6 → Call Stack에서 즉시 실행
  • Promise.then() await 이후 → Microtask Queue
  • setTimeout → Macrotask Queue → 마지막에 실행됨

4. 동기 실행처럼 보이지만, 실제로는 비동기인 이유?

  • then().then() 체이닝이나 await await 연속 구문은 동기처럼 보여도, 실제로는 모두 마이크로태스크 큐에 예약된 작업입니다.
  • 따라서 겉보기에는 순차적이지만, 본질은 비동기 처리입니다.

5. 마이크로태스크는 무엇인가?

  • 비동기 중에서도 가장 먼저 실행됨
  • 예: Promise.then, await, queueMicrotask
  • 콜스택이 비는 즉시 처리됨 → setTimeout보다 빠름

6. 결론

동기 코드 Call Stack 즉시 실행 console.log()
마이크로태스크 Microtask Queue Call Stack이 비자마자 Promise.then, await 이후
매크로태스크 Macrotask Queue Microtask가 끝난 후 setTimeout, setInterval

비동기 코드가 복잡하게 보일 수 있지만, 이벤트 루프의 실행 순서를 정확히 이해하면 대부분의 동작은 예측 가능합니다.

 

실제 프로젝트에서 async/await Promise, setTimeout이 섞일 때는 이 흐름을 바탕으로 디버깅하고, 예측 가능한 코드를 작성할 수 있습니다.