icon안동민 개발노트

JavaScript의 비동기 처리 복습


 JavaScript는 단일 스레드 언어이지만, 비동기 프로그래밍을 통해 효율적으로 I/O 작업을 처리할 수 있습니다.

 이 절에서는 JavaScript의 비동기 처리 방식을 복습하고, 주요 개념들을 살펴보겠습니다.

콜백 함수와 그 한계

 콜백 함수는 JavaScript에서 비동기 작업을 처리하는 가장 기본적인 방법입니다.

function fetchData(callback) {
  setTimeout(() => {
    callback('Data fetched');
  }, 1000);
}
 
fetchData((result) => {
  console.log(result);
});

 하지만 콜백 함수는 중첩될 경우 "콜백 지옥"이라 불리는 가독성 문제를 일으킬 수 있습니다.

fetchData1((result1) => {
  fetchData2(result1, (result2) => {
    fetchData3(result2, (result3) => {
      console.log(result3);
    });
  });
});

 이러한 문제를 해결하기 위해 Promise와 async/await가 도입되었습니다.

Promise

 Promise는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체입니다.

 Promise의 상태

  1. Pending : 초기 상태, 비동기 작업 진행 중
  2. Fulfilled : 작업 성공적 완료
  3. Rejected : 작업 실패

 Promise 사용 예제

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true;
      if (success) {
        resolve('Data fetched');
      } else {
        reject('Error fetching data');
      }
    }, 1000);
  });
}
 
fetchData()
  .then(result => console.log(result))
  .catch(error => console.error(error));

 Promise 메서드

  • then() : Promise가 이행되었을 때 실행될 콜백을 등록
  • catch() : Promise가 거부되었을 때 실행될 콜백을 등록
  • finally() : Promise의 성공/실패 여부와 관계없이 실행될 콜백을 등록

 Promise를 사용하면 콜백 지옥 문제를 어느 정도 해결할 수 있습니다.

fetchData1()
  .then(result1 => fetchData2(result1))
  .then(result2 => fetchData3(result2))
  .then(result3 => console.log(result3))
  .catch(error => console.error(error));

async / await

 async / await는 Promise를 기반으로 하는 더 직관적인 비동기 처리 문법입니다.

 기본 사용법

async function fetchAllData() {
  try {
    const result1 = await fetchData1();
    const result2 = await fetchData2(result1);
    const result3 = await fetchData3(result2);
    console.log(result3);
  } catch (error) {
    console.error(error);
  }
}
 
fetchAllData();

 동작 원리

  • async 함수는 항상 Promise를 반환합니다.
  • await 키워드는 Promise가 이행될 때까지 함수의 실행을 일시 중지합니다.

 에러 처리

 async / await에서는 try / catch 구문을 사용하여 에러를 처리할 수 있습니다.

Promise vs async / await

 차이점

  1. 문법 : Promise는 .then().catch()를 사용하고, async / await는 동기 코드와 유사한 형태를 가집니다.
  2. 에러 처리 : Promise는 .catch()를 사용하고, async / await는 try / catch를 사용합니다.
  3. 가독성 : async / await가 일반적으로 더 직관적이고 읽기 쉽습니다.

 Promise의 장단점

 장점

  • 체이닝을 통한 연속적인 비동기 작업 처리 가능
  • 광범위한 브라우저 지원

 단점

  • 복잡한 비동기 로직에서 여전히 가독성 문제 발생 가능
  • 에러 처리가 각 Promise마다 필요할 수 있음

 async / await의 장단점

 장점

  • 동기 코드와 유사한 직관적인 문법
  • try/catch를 사용한 일관된 에러 처리

 단점

  • 비교적 최신 문법으로 일부 환경에서 지원되지 않을 수 있음
  • 남용 시 성능 저하 가능성 (불필요하게 순차적 실행을 강제할 수 있음)

병렬 처리

 여러 비동기 작업을 동시에 처리해야 할 경우, Promise.all()이나 Promise.race()를 사용할 수 있습니다.

async function fetchAllData() {
  try {
    const [result1, result2, result3] = await Promise.all([
      fetchData1(),
      fetchData2(),
      fetchData3()
    ]);
    console.log(result1, result2, result3);
  } catch (error) {
    console.error(error);
  }
}

 이 방식은 여러 비동기 작업을 병렬로 실행하여 전체 실행 시간을 줄일 수 있습니다.

 JavaScript의 비동기 처리 방식은 웹 애플리케이션의 성능과 사용자 경험을 크게 향상시킬 수 있습니다.

 특히 React 애플리케이션에서 데이터 페칭, 외부 API 호출, 파일 처리 등의 작업을 수행할 때 이러한 비동기 처리 기법들이 광범위하게 사용됩니다.

 Promise와 async/await는 각각의 장단점이 있지만, 최근의 경향은 async/await를 선호하는 쪽으로 기울고 있습니다.

 async/await는 코드의 가독성을 높이고 동기 코드와 유사한 흐름으로 비동기 로직을 작성할 수 있게 해주기 때문입니다.

 그러나 여전히 Promise의 직접적인 사용이 더 적절한 경우도 있으며, 특히 Promise.all()이나 Promise.race() 같은 고급 기능을 사용할 때는 Promise를 직접 다루는 것이 필요합니다.

 따라서 두 방식 모두 잘 이해하고 상황에 맞게 적절히 사용하는 것이 중요합니다.