본문 바로가기

개발

JavaScript 비동기 처리: Promise vs async/await 완벽 가이드 📜

반응형

JavaScript는 단일 스레드 기반 언어이므로, 시간이 오래 걸리는 작업(예: 네트워크 요청, 파일 I/O)을 처리할 때 다른 작업을 멈추지 않도록 비동기 처리가 필수적입니다. 과거에는 콜백 함수를 사용했지만, 콜백이 중첩되어 코드가 복잡해지는 콜백 헬(Callback Hell)이라는 문제점이 있었습니다. 이러한 문제를 해결하기 위해 등장한 Promiseasync/await은 현대 JavaScript 비동기 처리의 핵심입니다.


1. Promise: 비동기 작업의 상태를 약속하다 🤝

Promise는 비동기 작업의 최종 성공 또는 실패를 나타내는 객체입니다. Promise 객체는 세 가지 상태 중 하나를 가집니다.

  • Pending (대기): 비동기 작업이 아직 완료되지 않은 초기 상태.
  • Fulfilled (이행): 작업이 성공적으로 완료된 상태.
  • Rejected (거부): 작업이 실패한 상태.

Promise의 가장 큰 장점은 비동기 작업의 성공과 실패를 명확하게 분리하여 관리할 수 있다는 점입니다. 콜백 함수를 중첩시키는 대신, .then()과 .catch()를 체인으로 연결하여 코드를 더 직관적으로 작성할 수 있습니다. 이를 통해 콜백 헬 문제를 해결하고, 비동기 작업 중 발생하는 모든 오류를 한 곳에서 처리할 수 있습니다.

Promise 코드 예시:

function fetchData(url) {
  return new Promise((resolve, reject) => {
    // 실제 비동기 작업 (예: API 호출)
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = function() {
      if (xhr.status === 200) {
        resolve(xhr.responseText); // 작업 성공 시 데이터 반환
      } else {
        reject(new Error(xhr.statusText)); // 작업 실패 시 오류 반환
      }
    };
    xhr.onerror = function() {
      reject(new Error('네트워크 오류가 발생했습니다.'));
    };
    xhr.send();
  });
}

fetchData('https://api.example.com/data')
  .then(data => {
    console.log("데이터를 성공적으로 가져왔습니다:", data);
    return fetchData('https://api.example.com/other-data'); // 또 다른 Promise 반환
  })
  .then(secondData => {
    console.log("두 번째 데이터를 성공적으로 가져왔습니다:", secondData);
  })
  .catch(error => {
    console.error("비동기 작업 중 오류 발생:", error.message);
  });

Promise는 비동기 작업을 순차적으로 처리할 때 .then() 체인을 계속 연결해야 하므로 코드가 길어질 수 있다는 한계가 있습니다. 또한, 여전히 콜백 기반의 느낌이 남아 있어 동기 코드처럼 직관적이지는 않습니다.


2. async/await: 비동기 코드를 동기 코드처럼 🖋️

async/await은 ES2017에 도입된 문법으로, Promise를 기반으로 한 문법적 설탕(Syntactic Sugar)입니다. 이 문법은 비동기 코드를 마치 동기 코드처럼 읽고 쓸 수 있게 하여 가독성을 혁신적으로 향상시킵니다.

  • async 키워드: 함수 앞에 async를 붙이면, 해당 함수는 항상 Promise를 반환합니다. 이 함수는 비동기적으로 동작하며, await 키워드를 사용할 수 있게 해줍니다.
  • await 키워드: async 함수 내에서만 사용할 수 있으며, await 뒤에 오는 Promise가 이행(fulfilled)될 때까지 해당 함수의 실행을 일시 중지합니다.

async/await의 가장 큰 장점은 비동기 작업의 순서를 명확하게 표현할 수 있다는 점입니다. try...catch 블록을 사용해 동기 코드와 동일한 방식으로 오류를 처리할 수 있어 코드가 훨씬 간결하고 유지보수하기 쉬워집니다.

async/await 코드 예시:

async function fetchAndProcessData() {
  try {
    console.log("첫 번째 데이터 가져오기 시작...");
    const response1 = await fetch("https://api.example.com/data");
    const data1 = await response1.json();
    console.log("첫 번째 데이터 성공:", data1);

    console.log("두 번째 데이터 가져오기 시작...");
    const response2 = await fetch("https://api.example.com/other-data");
    const data2 = await response2.json();
    console.log("두 번째 데이터 성공:", data2);
    
    // 두 데이터를 이용한 최종 작업
    const finalResult = {
      message: "모든 데이터 처리가 완료되었습니다.",
      combinedData: { ...data1, ...data2 }
    };
    return finalResult;
    
  } catch (error) {
    console.error("오류가 발생했습니다:", error);
    // 오류 발생 시 추가적인 처리가 가능
    throw new Error("데이터를 처리할 수 없습니다.");
  }
}

fetchAndProcessData()
  .then(result => {
    console.log("최종 결과:", result);
  })
  .catch(err => {
    console.log("최종 처리 실패:", err.message);
  });
반응형