웹개발/javascript

[자바스크립트] 비동기 처리 순서

joamashi 2025. 1. 20. 20:09

const response = fetch('https://jsonplaceholder.typicode.com/todos/1');
// const data = response.json();
console.log('0', response);    

const response2 = fetch('https://jsonplaceholder.typicode.com/todos/1');
const data2 = response2;
console.log('2', data2);

fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  body: JSON.stringify({
    title: 'foo',
    body: 'bar',
    userId: 1,
  }),
  headers: {
    'Content-type': 'application/json; charset=UTF-8',
  },
})
  .then((response) => response.json())
  .then((json) => console.log('3', json));

(async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  const data = await response;
  console.log('1', data);
})()

코드 실행 흐름 분석:

  1. 첫 번째 fetch 호출 (로그 '0')
    • fetch 함수는 비동기 함수이며 즉시 Promise 객체를 반환함.
    • 따라서 console.log('0', response);가 즉시 실행되며, Promise<pending> 상태의 response가 출력됨.
    • 네트워크 요청은 백그라운드에서 진행됨.
  2. 두 번째 fetch 호출 (로그 '2')
    • 마찬가지로 즉시 실행되며, Promise<pending> 상태가 출력됨.
  3. 세 번째 fetch 호출 (로그 '3')
    • fetch가 호출되고, .then 체이닝이 등록됨.
    • 네트워크 요청이 완료되면 응답을 JSON으로 변환 후 console.log('3', json);이 실행됨.
    • 이 과정은 이벤트 루프에 의해 비동기적으로 처리되므로 이후 코드 실행 후에 로그가 출력됨.
  4. async/await 비동기 호출 (로그 '1')
    • 이 함수는 즉시 실행되지 않고, 비동기 함수의 실행 컨텍스트가 이벤트 루프에 의해 예약됨.
    • await는 네트워크 응답을 기다리므로 블로킹되지 않으며, 나머지 동기 코드를 먼저 실행시킴.
    • 네트워크 요청이 끝난 후 console.log('1', data);가 실행됨.

왜 console.log('1')이 console.log('3')보다 먼저 출력될까?

  1. await는 호출된 위치에서 해당 비동기 작업이 완료될 때까지 기다리지만, Promise.then 체인은 이벤트 루프의 마이크로태스크 큐에서 처리됨.
  2. await는 내부적으로 마이크로태스크로 동작하므로, Promise 체인보다 빠르게 실행될 가능성이 있음.
  3. fetch().then().then() 구문은 Promise 체인이므로, 마이크로태스크에서 처리되지만 일반적으로 await보다는 우선순위가 조금 낮음.
  4. 네트워크 요청 속도나 브라우저 스케줄링에 따라 약간의 차이가 있을 수 있음.

실행 예상 순서:

  1. console.log('0', Promise), console.log('2', Promise) → 동기 코드이므로 즉시 실행
  2. console.log('1', data) → await로 비동기 완료 후 실행 (마이크로태스크 우선순위 높음)
  3. console.log('3', json) → Promise 체인으로 처리되므로 나중에 실행

정리:

  • async/await는 마이크로태스크 우선순위가 높아 then 체인보다 먼저 실행될 수 있음.
  • fetch 호출은 비동기로 작동하며 즉시 Promise<pending>을 반환하지만, 결과를 기다릴 때 await가 먼저 처리됨.
  • 네트워크 상태나 브라우저의 이벤트 루프 스케줄링에 따라 미세한 차이가 발생할 수 있음.

 

마이크로태스크 큐(Microtask Queue)란?

마이크로태스크 큐는 JavaScript의 **이벤트 루프(Event Loop)**에서 작업을 처리하는 두 가지 주요 큐(매크로태스크 큐, 마이크로태스크 큐) 중 하나로, 우선순위가 높은 작업을 실행하는 역할을 함.

마이크로태스크는 주로 비동기 Promise 처리 및 await와 관련된 작업이 들어가며, 현재 실행 중인 태스크(스크립트)가 끝난 후 즉시 실행되기 때문에 다른 비동기 작업보다 빠르게 실행됨.


마이크로태스크 큐의 주요 특징

  1. 우선순위가 높음
    • 매크로태스크(예: setTimeout, setInterval)보다 항상 먼저 실행됨.
  2. 비동기 작업 종료 후 즉시 실행
    • 현재 실행 중인 동기 작업(콜 스택)이 완료된 후 바로 실행됨.
  3. 주요 비동기 API와 연관
    • Promise.then(), catch(), finally(), MutationObserver, queueMicrotask() 등이 마이크로태스크로 들어감.

마이크로태스크의 실행 순서 예제

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() → 매크로태스크 큐에 등록됨 (콜백 실행 대기 중).
  3. Promise.resolve().then() → 마이크로태스크 큐에 등록됨 (실행 대기 중).
  4. console.log('D') → 동기 코드, 즉시 실행.
  5. 마이크로태스크(then)가 먼저 실행되므로 C 출력.
  6. 매크로태스크(setTimeout)가 실행되어 B 출력.

마이크로태스크와 매크로태스크 비교

특징 마이크로태스크 매크로태스크
우선순위 높음 (Promise, await) 낮음 (setTimeout, setInterval)
실행 시점 현재 실행 중인 작업 완료 직후 실행 모든 마이크로태스크 후 실행
주요 API Promise.then(), queueMicrotask() setTimeout(), setInterval(), I/O
예제 코드 결과 즉시 실행되지만 동기 코드 이후 수행 이후 스케줄링을 통해 수행

 


마이크로태스크 실행 흐름

  1. 콜 스택에 있는 동기 코드를 모두 실행.
  2. 마이크로태스크 큐가 비어 있을 때까지 실행.
  3. 매크로태스크(예: setTimeout) 실행.
  4. 다시 마이크로태스크 확인 및 실행.
  5. 반복...

queueMicrotask()를 사용한 마이크로태스크 등록

queueMicrotask()를 사용하면 명시적으로 마이크로태스크 큐에 작업을 추가할 수 있음.

 
console.log('Start');

queueMicrotask(() => {
  console.log('Microtask 1');
});

console.log('End');

// 실행 결과
Start
End
Microtask 1

마이크로태스크 활용 시 주의점

  1. 무한 루프 방지:
    • 마이크로태스크가 계속 생성되면 이벤트 루프가 다른 작업을 실행하지 못함.
    • 예: 무한 Promise.then() 체이닝.
  2. 비효율적인 태스크 등록:
    • 필요 이상으로 마이크로태스크를 많이 등록하면 성능 저하 발생.

정리

  • 마이크로태스크는 비동기 작업 중 가장 빠르게 실행되는 큐이며, 주요 비동기 API(Promise, await)와 연관됨.
  • 동기 코드 실행 후 바로 마이크로태스크가 실행되고, 그 이후 매크로태스크가 실행됨.
  • queueMicrotask()를 사용하여 명시적으로 마이크로태스크 등록이 가능함.
728x90