3 분 소요

우주메타 스터디5 - 비동기

[5주차] 비동기 Discussions

1. 비동기가 필요한 이유

  • 오래 걸리는 작업이 전체 프로그램을 멈추지 않도록 하기 위해 꼭 필요하다.
  • 사용자 경험(UX)을 지키는 가장 기본적인 구조이다.


[예제 코드]

  • fetchData()는 비동기 함수이므로 즉시 Promise를 반환하고,
  • 내부의 await해당 줄 이후 실행을 “일시 중단”시킨다.
  • 하지만 전체 코드의 흐름은 멈추지 않고 아래 코드(console.log("2"))가 먼저 실행된다.
  • 나중에 getUser()가 완료되면 console.log(data)가 실행된다.
async function fetchData() {
  const data = await getUser(); // 여기서 함수 내부 실행이 잠시 중단됨
  console.log(data);
}

console.log("1");
fetchData(); // 이 함수는 비동기 : 다른 것이 먼저 실행됨
console.log("2");

[결과]

1
2
{user data}



2. 비동기 역사

  • 자바스크립트는 초기부터 비동기 처리를 위해 콜백 함수(callback)를 사용했다. 하지만 콜백 안에 또 콜백이 중첩되는 “콜백 지옥” 문제로 인해 가독성과 유지보수성이 나빠졌다.
  • 이를 해결하기 위해 Promise가 도입되었고, 체이닝 방식으로 비동기를 처리할 수 있게 되었다. 그러나 .then()이 중첩되거나 복잡한 흐름에서는 여전히 가독성 문제가 생겼다.
  • 이를 보완하기 위해 async/await이 등장해 동기처럼 비동기 코드를 작성할 수 있게 되었다.



3. 이벤트 루프

  • 이벤트 루프는 자바스크립트의 비동기 처리 메커니즘을 관리하는 논리적인 구조로, 콜 스택과 태스크 큐(마이크로태스크, 매크로태스크) 사이의 조율자 역할을 한다.
  • 자바스크립트는 싱글 스레드이기 때문에, 이벤트 루프는 콜 스택이 비어 있는지 지속적으로 확인하며, 비어 있는 경우 큐에 대기 중인 작업을 콜 스택으로 옮겨 실행한다.
  • 콜 스택은 자바스크립트 엔진 내부에서 실행 흐름을 관리하며, 태스크 큐는 브라우저(Web APIs)나 Node.js에서 비동기 작업 완료 후 콜백을 등록해두는 공간이다.
  • 큐 자체는 브라우저나 Node.js 런타임에서 관리되지만, 해당 큐의 실행 타이밍을 결정하는 것은 이벤트 루프이다.



4. 마이크로태스크 큐 vs 태스크 큐

  • 마이크로태스크 큐 : 두 개의 큐 중 가장 먼저 처리된다. 우선순위가 가장 높고, 큐가 실행되면 내부에 실행될 동작들이 전부 실행되는 특징이 있다. Promise, async await 등의 콜백 함수가 이 큐에서 실행된다.
  • 매크로태스크 큐 : 마이크로태스크 큐보다 우선순위가 낮고, 내부 동작들이 하나씩 실행된다. 콜백은 다음 루프로 넘어간 뒤 실행된다. 종류로는 setTimeout, DOM events 등이 있다.


4-1. 매크로태스크 큐와 마이크로태스크 큐는 왜 나눠져 있을까?

1. 작업의 중요도와 순서를 보장하기 위해

  • Microtask는 주로 현재 실행 중인 작업의 “후속 처리”이다. (ex: then, await)
  • Macrotask“다음 일”을 처리한다. (ex: setTimeout, 사용자 입력 등)
  • 👉 Microtask는 현재 실행 중인 코드와 논리적으로 밀접하게 연결되어 있기 때문에, 브라우저는 렌더링이나 다음 task 전에 반드시 먼저 처리하려고 우선순위를 높인다.

2. 렌더링 타이밍을 컨트롤하기 위해

  • Microtask는 렌더링 사이에 끼어들 수 있다.
  • Macrotask는 렌더링 후에 실행된다.


4-2. 만약 매크로태스크 큐의 작업 실행 중 우선순위가 더 높은 마이크로태스크 큐 작업이 들어오게 된다면?

  • 매크로태스크 하나가 시작되면 끝날 때까지 마이크로태스크는 개입하지 못한다.
  • 하지만 매크로태스크 내부에서 마이크로태스크를 예약하면, 그 마이크로태스크는 현재 매크로태스크가 끝난 직후에 실행된다.
setTimeout(() => {
  console.log("매크로 1 시작");

  // 매크로태스크 실행 중 생성된 마이크로태스크
  Promise.resolve().then(() => {
    console.log("마이크로 동작");
  });

  console.log("매크로 1 끝");
}, 0);
매크로 1 시작
매크로 1 끝
마이크로 동작

setTimeout(() => {
  console.log("매크로 1");

  Promise.resolve().then(() => console.log("마이크로 A"));
  Promise.resolve().then(() => console.log("마이크로 B"));
}, 0);

setTimeout(() => {
  console.log("매크로 2");
}, 0);
매크로 1
마이크로 A
마이크로 B
매크로 2



5. await과 then의 차이점

  • 둘 다 Promise를 처리한다.
  • 둘 다 비동기 작업 이후의 로직을 실행할 수 있다.
구분 await .then()
문법 async 함수 안에서만 사용 가능 어디서든 사용 가능
가독성 동기 코드처럼 작성 가능 콜백 중첩으로 복잡해질 수 있음
예외 처리 방식 try/catch 사용 .catch() 사용
실행 흐름 제어 코드 실행이 해당 지점에서 잠시 중단됨 비동기적으로 다음 .then()으로 흐름 이동



6. Promise.all vs Promise.allSettled vs Promise.race

  • Promise.all : 병렬로 처리하는 프로미스 중 하나라도 실패하면 모두 reject 된 것으로 간주하며 나머지 결과들은 무시되어 reject만 반환된다. 실행되는 비동기 순서는 보장되지 않으며, 단순히 여러 개의 요청을 한 번에 처리해야 할 때 사용된다.
  • Promise.allSettled : all과는 반대로 전체 프로미스의 결과가 모두 반환된다. 각 결과는 독립적이고, 하나의 요청이 실패해도 전체 promise의 결과에는 영향을 주지 않는다.
  • Promise.race : 여러 개의 프로미스를 병렬로 처리하고, 가장 첫 번째로 완료된 프로미스의 결과값을 반환한다.



7. fetch의 동작 원리

  • fetch() 함수는 비동기적으로 네트워크 요청을 보내는 Web API이다.
  • 요청을 보내고 나면, 서버가 응답을 줄 때까지 기다리지 않고 곧바로 Promise를 반환한다.
const responsePromise = fetch("/api/data");
console.log(responsePromise); // → Promise { <pending> }

이렇게 fetch()Response 객체를 담고 있는 Promise를 반환한다.


7-1. response.json()도 비동기인 이유

  • fetch()가 반환한 Response 객체는 응답 전체를 다 처리한 상태가 아니다.
  • response.json()은 응답 본문(body)을 JSON으로 파싱하는 비동기 작업이라서 또 Promise를 반환한다.
  • 즉, JSON으로 변환하는 작업도 네트워크에서 받은 데이터를 내부에서 처리해야 하므로 비동기이다.
async function fetchData() {
  const response = await fetch("/api/data"); // 1. 서버에 요청 보내고 응답 받을 때까지 기다림
  const data = await response.json(); // 2. 응답 body를 JSON으로 파싱 완료될 때까지 기다림
  console.log(data);
}



8. 에러바운더리가 감지하지 못하는 범위

리액트의 에러바운더리는 컴포넌트 트리에서 UI 렌더링 중 발생하는 렌더링 오류만 감지한다.

  • 이벤트 핸들러 : React는 이벤트 핸들러 내부 오류를 자동으로 감싸지 않기 때문이다.
  • 비동기 코드 : 비동기 함수 실행 시점은 컴포넌트 렌더링과 무관하기 때문이다.
  • 서버 사이드 렌더링 : 에러바운더리는 클라이언트 사이드에서만 동작하기 때문이다.
  • 에러 경계 자체에서 발생하는 에러 : 에러를 상위로 던지는 것이기 때문에 위에서 잡을 수 없다면 throw로 던져도 감지하지 못한다. → 상위에 에러바운더리로 감싸야 한다.

카테고리:

업데이트:

댓글남기기