Skip to content

제4.5장: Async/await vs Promise

학습 시간: 30분


1. 비동기성: "우주 통신"을 관리하는 두 가지 방법

지상 관제 센터(ЦУП)가 화성에 명령을 보낸다고 상상해 보세요. 응답은 몇 분 후에나 도착할 것입니다. 이 시간 동안 작업을 어떻게 구성해야 할까요?

방법 1: "콜백 프로토콜" (Promise와 .then()) 명령을 보내고 지시합니다: "응답이 올 때마다 이 함수를 실행하세요." 이는 이벤트 체인과 유사합니다.

방법 2: "대기 모드" (Async/await) "이 명령에 대한 응답을 기다리겠지만, 다른 제어판을 차단하지는 않을 것입니다." 라고 말하는 것과 같습니다. 이는 이 특정 작업의 실행을 일시 중지하여 나머지 지상 관제 센터가 계속 작동하도록 허용하는 것과 같습니다.

두 가지 방법 모두 비동기 작업 관리라는 동일한 문제를 해결합니다. async/await는 Promise 위에 구축된 더 현대적이고 읽기 쉬운 구문일 뿐입니다.

💡 우주 비유:

  • Promise와 .then(): 이것은 "탐사선이 사진을 보내면 분석 부서로 전달하라"고 스티커에 쓰는 것과 같습니다.
  • Async/await: 이것은 조수에게 "탐사선으로부터 사진을 기다려줘. 나는 그동안 새 로켓 발사를 위한 계산을 할게."라고 말하는 것과 같습니다.

2. Promise와 .then(): 고전적인 명령 체인

이것은 JavaScript에서 비동기성을 다루는 기본적인 방법이며, 우리는 4.1장에서 이를 사용했습니다.

첫 번째 코드를 다시 살펴봅시다:

function getIssPositionWithPromises() {
    console.log('"Promise" 프로토콜로 요청을 보내는 중...');

    fetch('http://api.open-notify.org/iss-now.json')
        .then(response => {
            // 단계 1: 응답 수신
            if (!response.ok) {
                throw new Error(`HTTP 오류: ${response.status}`);
            }
            return response.json(); // 새 Promise 반환
        })
        .then(data => {
            // 단계 2: 데이터 파싱됨
            console.log('"Promise" 프로토콜로 데이터 수신됨:', data.iss_position);
        })
        .catch(error => {
            // 단계 3 (오류): 어느 단계에서든 문제가 발생했습니다
            console.error('"Promise" 프로토콜 통신 실패:', error);
        });

    console.log('...명령이 전송되었습니다, 지상 관제 센터는 계속 작동합니다...');
}

장점:

  • 명시적인 작업 체인.
  • 간단한 순차적 작업에 적합합니다.

단점:

  • "콜백 헬" (Callback Hell): 중첩된 비동기 작업이 많을 경우 코드가 읽기 어려운 .then()의 "사다리" 형태로 변할 수 있습니다.
  • 오류 처리가 덜 직관적일 수 있습니다.

3. Async/await: 현대적인 동기식 스타일

async/await는 Promise 위에 있는 "문법적 설탕"으로, 비동기 코드를 마치 동기 코드처럼 작성할 수 있게 해줍니다.

사용 규칙:

  1. 키워드 awaitasync로 표시된 함수 내부에서만 사용할 수 있습니다.
  2. await는 Promise를 반환하는 호출(예: fetch() 또는 response.json()) 앞에 놓입니다.
  3. await는 Promise가 해결될 때까지 async 함수의 실행을 "일시 중지"하고 그 결과를 반환합니다.

동일한 코드를 async/await로 다시 작성:

async function getIssPositionWithAsyncAwait() {
    console.log('"Async/await" 프로토콜로 요청을 보내는 중...');

    try {
        // 단계 1: 서버 응답 대기
        const response = await fetch('http://api.open-notify.org/iss-now.json');

        if (!response.ok) {
            throw new Error(`HTTP 오류: ${response.status}`);
        }

        // 단계 2: 응답 본문이 JSON으로 변환될 때까지 대기
        const data = await response.json();

        console.log('"Async/await" 프로토콜로 데이터 수신됨:', data.iss_position);
    } catch (error) {
        // 단계 3 (오류): try 블록에서 발생하는 모든 오류 포착
        console.error('"Async/await" 프로토콜 통신 실패:', error);
    }

    console.log('...명령이 전송되었습니다, 지상 관제 센터는 계속 작동합니다...');
}

장점:

  • 가독성: 코드가 거의 일반 동기 코드처럼 보여 위에서 아래로 읽기 쉽습니다.
  • 오류 처리: 표준적이고 익숙한 try...catch 블록이 사용됩니다.
  • 디버깅: await가 있는 각 줄에 중단점(breakpoints)을 설정할 수 있어 디버깅이 훨씬 쉽습니다.

단점:

  • await 또는 async를 잊기 쉬워 오류로 이어질 수 있습니다.

4. 어떤 프로토콜을 사용해야 할까요?

상황 권장되는 접근 방식 이유?
대부분의 경우 async/await 코드가 더 깔끔하고 읽고 디버깅하기 쉽습니다. 이것이 현대적인 표준입니다.
간단한 1-2단계 체인 Promise와 .then() 충분히 적합하며 코드가 간결하게 유지됩니다.
여러 요청의 병렬 실행 Promise.all() 이 메서드는 여러 Promise를 동시에 시작하고 모두 완료될 때까지 기다릴 수 있게 합니다. async/await는 이와 잘 어울립니다.

Promise.all() 예시:

async function getParallelData() {
    try {
        // 두 요청을 동시에 시작합니다.
        const [shipsResponse, launchesResponse] = await Promise.all([
            fetch('https://api.spacexdata.com/v4/rockets'),
            fetch('https://api.spacexdata.com/v4/launches/latest')
        ]);

        if (!shipsResponse.ok || !launchesResponse.ok) {
            throw new Error('요청 중 하나가 실패했습니다!');
        }

        const rockets = await shipsResponse.json();
        const latestLaunch = await launchesResponse.json();

        console.log(`총 함대 로켓 수: ${rockets.length}`);
        console.log(`최신 발사: ${latestLaunch.name}`);
    } catch (error) {
        console.error('병렬 데이터 가져오기 오류:', error);
    }
}


개념 확인 퀴즈

1. `async/await`는...

2. `await`가 사용되는 함수 내부에 반드시 필요한 키워드는 무엇입니까?

3. `.then()`에 대한 `async/await`의 주요 이점은 무엇입니까?

4. `async` 함수 내에서 `fetch()` 앞에 `await`를 잊으면 어떻게 됩니까?

5. `Promise.all()`은 무엇을 위해 사용됩니까?


🚀 장 요약:

비동기 작업을 관리하기 위한 두 가지 구문을 학습했으며, 대부분의 현대 프로젝트에서 async/await가 왜 선호되는지 이해했습니다.

  • 🔗 Promise.then()에 대한 지식을 복습했습니다.
  • 🛠️ async/await의 작동 방식과 그 장점을 깊이 이해했습니다.
  • ⚡ 병렬 요청을 실행하기 위한 Promise.all에 대해 알게 되었습니다.

통신 프로토콜 학습 완료! 이 섹션의 마지막 장에서 우리는 모든 지식을 한데 모아 모든 CRUD 작업에 대한 완전한 인터페이스를 생성하여 우리의 "비행 제어 센터"를 완성할 것입니다.

📌 연습 문제:

  • 당신의 app.js 파일에서 여전히 .then()을 사용하는 모든 함수를 async/await 구문으로 다시 작성하세요.
  • Promise.all()에 다른 요청을 추가해 보세요(예: https://api.spacexdata.com/v4/starlink로) 그리고 데이터를 출력하세요.

⚠️ 오류 발생 시:

  • await is only valid in async functions: await를 사용하는 함수가 async로 표시되어 있는지 확인하세요.
  • 변수에 [object Promise]가 포함됨: 프라미스를 반환하는 함수 앞에 await를 붙이는 것을 잊으셨습니다.