메뉴 닫기

비동기 처리 async await 가독성과 예외 처리를 쉽게 하는 방법

비동기 처리 async await 가독성과 예외 처리를 쉽게 하는 방법

⚡ Promise보다 직관적인 흐름 제어와 에러 관리, async await로 해결하세요

자바스크립트에서 비동기 코드를 다루다 보면 콜백 함수가 중첩되거나 then 체인이 길어지면서 코드를 읽고 관리하기가 힘들어집니다.
특히 예외 처리 부분에서는 코드가 더 복잡해지는 경우가 많죠.
이런 문제를 해결하기 위해 등장한 것이 바로 async await 문법입니다.
마치 동기 코드처럼 읽히는 구조 덕분에 초보 개발자도 직관적으로 이해할 수 있고, 유지 보수 역시 한결 쉬워집니다.

이 글에서는 async await의 기본 개념부터 장점, 사용법, 그리고 실무에서 자주 마주치는 예외 처리까지 자세히 다루겠습니다.
Promise와의 차이점, 코드 예시를 통한 비교, 실제 개발에서의 활용법을 차근차근 살펴보며 비동기 처리의 핵심을 확실하게 이해할 수 있도록 정리했습니다.
비동기 코드를 보다 깔끔하고 안정적으로 작성하고 싶다면 끝까지 읽어보시길 추천합니다.



🔗 async await란 무엇인가?

자바스크립트에서 async await는 비동기 코드를 작성할 때 사용되는 최신 문법입니다.
원래는 Promise 객체를 통해 비동기 작업을 처리했지만, then 체인이 길어지면 코드의 가독성이 급격히 떨어지는 문제가 있었습니다.
async await는 이러한 문제를 개선하기 위해 ES2017(ES8)에서 도입되었으며, 비동기 코드를 마치 동기 코드처럼 작성할 수 있게 해줍니다.

async 키워드는 해당 함수가 비동기 함수임을 선언하고, await 키워드는 Promise가 처리될 때까지 기다렸다가 결과를 반환합니다.
이 덕분에 복잡한 비동기 흐름도 직관적으로 이해할 수 있으며, 코드가 순차적으로 실행되는 것처럼 보이게 만듭니다.
즉, 비동기 로직의 가독성과 유지보수성을 크게 향상시켜주는 도구라 할 수 있습니다.

💬 async await는 Promise를 기반으로 동작하지만, 문법적으로 더 간단하고 직관적인 방식을 제공합니다.

CODE BLOCK
// async 함수 선언
async function fetchData() {
    try {
        let response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
        let data = await response.json();
        console.log(data);
    } catch (error) {
        console.error("에러 발생:", error);
    }
}

위의 예시는 fetch API를 활용해 데이터를 불러오는 간단한 예제입니다.
await 키워드를 통해 fetch 함수의 응답이 완료될 때까지 기다렸다가 결과를 반환하기 때문에, 마치 동기식 코드처럼 읽히는 것이 특징입니다.
이처럼 async await는 네트워크 요청, 파일 처리, DB 조회 등 다양한 비동기 작업에서 활용됩니다.

🛠️ Promise와 async await의 차이

비동기 처리 방식의 핵심은 결국 Promise입니다.
async await는 Promise를 기반으로 동작하지만, 문법적 표현 방식에서 큰 차이를 보입니다.
Promise는 then과 catch 메서드를 연결하여 작업의 흐름을 제어하는 반면, async await는 동기 코드처럼 순차적으로 실행됩니다.

즉, 두 문법의 가장 큰 차이는 가독성과 예외 처리 방식입니다.
Promise 체인은 작업이 많아질수록 then이 연속적으로 이어져 가독성이 떨어지고, 중첩 구조가 발생하기 쉽습니다.
반대로 async await는 try catch 구문을 사용해 에러를 관리하므로 훨씬 직관적이고 깔끔한 코드를 작성할 수 있습니다.

CODE BLOCK
// Promise 방식
fetch("https://jsonplaceholder.typicode.com/posts/1")
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error("에러 발생:", error));

// async await 방식
async function getData() {
    try {
        const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error("에러 발생:", error);
    }
}

위의 두 코드 예제를 비교해보면 async await 방식이 훨씬 간결하고 가독성이 좋습니다.
특히 여러 개의 비동기 작업을 순차적으로 실행할 때 async await는 코드의 흐름이 자연스럽게 이어져 초보자도 쉽게 이해할 수 있습니다.

💎 핵심 포인트:
Promise는 유연하지만 복잡할 수 있고, async await는 직관적이어서 유지보수에 유리합니다.



⚙️ async await 기본 문법과 활용법

async await를 올바르게 이해하기 위해서는 두 가지 키워드의 의미를 먼저 알아야 합니다.
async는 함수를 비동기 함수로 정의하는 예약어이며, 해당 함수는 자동으로 Promise를 반환합니다.
await는 Promise가 처리될 때까지 기다린 뒤 결과값을 반환하는 역할을 합니다.
따라서 비동기 작업이 마치 동기 코드처럼 순차적으로 실행되는 것처럼 보이게 만들 수 있습니다.

📌 async 함수 기본 구조

CODE BLOCK
async function example() {
    return "Hello Async";
}

example().then(result => console.log(result)); // Hello Async

위 코드에서 example 함수는 async로 선언되어 자동으로 Promise를 반환합니다.
즉, 반환값은 항상 Promise 형태로 감싸져 있으며 then을 통해 결과를 받아올 수 있습니다.

📌 await 키워드의 사용

CODE BLOCK
async function getNumber() {
    let result = await Promise.resolve(10);
    console.log(result); // 10
}

await 키워드는 async 함수 안에서만 사용할 수 있으며, Promise가 처리될 때까지 코드 실행을 일시 중단합니다.
이 방식 덕분에 비동기 처리의 흐름을 더 쉽게 파악할 수 있습니다.

💡 TIP: async await는 비동기 작업을 직관적으로 작성할 수 있지만, 동시에 여러 작업을 실행해야 할 경우에는 Promise.all() 같은 메서드와 함께 사용하는 것이 효율적입니다.

🔌 try catch로 하는 예외 처리

비동기 코드에서 가장 중요한 부분 중 하나는 예외 처리입니다.
Promise 체인에서는 .catch() 메서드를 사용하지만, async await에서는 try catch 구문을 활용합니다.
이 방식은 동기 코드의 예외 처리와 동일한 형태이기 때문에 직관적이고 코드의 가독성이 크게 향상됩니다.

CODE BLOCK
async function fetchUser(id) {
    try {
        const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
        if (!response.ok) {
            throw new Error("네트워크 응답에 문제가 발생했습니다");
        }
        const user = await response.json();
        console.log("사용자 데이터:", user);
    } catch (error) {
        console.error("에러 발생:", error.message);
    } finally {
        console.log("요청이 완료되었습니다.");
    }
}

위 코드에서 보듯이 try 블록에서는 정상적인 실행을, catch 블록에서는 에러 발생 시의 처리를 담당합니다.
또한 finally 블록을 통해 요청의 성공 여부와 상관없이 반드시 실행되어야 하는 작업(예: 리소스 해제, 로딩 상태 초기화)을 처리할 수도 있습니다.

  • 🛠️try 블록 안에서 비동기 작업 실행
  • ⚙️catch 블록에서 에러 처리
  • 🔌finally 블록에서 공통 작업 수행

⚠️ 주의: try catch를 남용하면 코드가 불필요하게 길어질 수 있습니다.
에러 발생 가능성이 높은 부분에만 적절히 사용하는 것이 바람직합니다.



💡 실무에서 자주 쓰이는 패턴과 예제

async await는 단순히 문법을 아는 것만으로는 충분하지 않습니다.
실무에서는 여러 개의 비동기 작업을 병렬로 처리하거나, 특정 조건에 맞게 순차 실행하는 등의 패턴이 자주 활용됩니다.
이런 패턴을 이해하면 코드 효율성과 성능을 동시에 높일 수 있습니다.

📌 여러 비동기 작업을 동시에 실행하기

여러 API 요청이나 데이터베이스 쿼리를 동시에 처리해야 할 때는 Promise.all()과 함께 async await를 사용하는 것이 일반적입니다.
이 방법은 네트워크 요청을 병렬로 처리하여 속도를 크게 단축할 수 있습니다.

CODE BLOCK
async function fetchPostsAndUsers() {
    try {
        const [posts, users] = await Promise.all([
            fetch("https://jsonplaceholder.typicode.com/posts").then(res => res.json()),
            fetch("https://jsonplaceholder.typicode.com/users").then(res => res.json())
        ]);
        console.log("게시글 개수:", posts.length);
        console.log("사용자 개수:", users.length);
    } catch (error) {
        console.error("에러 발생:", error);
    }
}

📌 순차 실행 패턴

모든 작업을 병렬로 처리하는 것이 아니라, 특정 순서에 따라 실행해야 하는 경우도 많습니다.
이때는 await를 적절히 배치하여 흐름을 제어합니다.

CODE BLOCK
async function sequentialTasks() {
    const step1 = await Promise.resolve("1단계 완료");
    console.log(step1);

    const step2 = await Promise.resolve("2단계 완료");
    console.log(step2);

    const step3 = await Promise.resolve("3단계 완료");
    console.log(step3);
}

💎 핵심 포인트:
병렬 처리에는 Promise.all(), 순차 실행에는 단순 await 구문을 사용하면 효율적인 비동기 코드 작성이 가능합니다.

📌 실무에서의 활용 사례

실무에서는 API 연동, 사용자 인증, 파일 업로드, 로그 수집 등 다양한 작업에서 async await를 적극적으로 사용합니다.
특히 데이터 요청과 응답이 많은 SPA(Single Page Application) 환경에서는 코드의 가독성과 유지보수성 향상 효과가 크기 때문에 필수적인 문법으로 자리 잡고 있습니다.

자주 묻는 질문 (FAQ)

async await는 Promise를 완전히 대체하나요?
완전히 대체하는 것은 아닙니다. async await는 Promise를 기반으로 동작하기 때문에, 특정 상황에서는 Promise 메서드를 함께 사용하는 것이 더 효율적일 수 있습니다.
await 키워드는 어디에서나 사용할 수 있나요?
await는 반드시 async 함수 내부에서만 사용할 수 있습니다. 함수 바깥이나 일반 코드 블록에서는 사용할 수 없습니다.
try catch 없이 에러를 처리할 수 있나요?
가능합니다. async 함수에서 반환된 Promise에 대해 .catch()를 사용할 수도 있습니다. 하지만 가독성과 유지보수를 위해 try catch 사용이 더 일반적입니다.
Promise.all과 async await는 어떻게 같이 쓰이나요?
Promise.all은 여러 비동기 작업을 병렬 실행할 때 유용합니다. async await와 결합하면 직관적인 코드로 병렬 실행을 효율적으로 처리할 수 있습니다.
await는 코드 실행을 멈추게 하나요?
await는 해당 Promise가 처리될 때까지 함수 내부 실행을 일시 정지합니다. 하지만 전체 프로그램이 멈추는 것은 아니며, 다른 작업은 계속 진행됩니다.
async await를 쓰면 성능이 더 좋아지나요?
성능 자체가 향상되는 것은 아닙니다. 다만 코드의 가독성과 유지보수성이 높아지고, 에러 관리가 쉬워지는 장점이 있습니다.
async await는 모든 브라우저에서 지원되나요?
최신 브라우저에서는 대부분 지원되지만, 구형 브라우저에서는 지원되지 않을 수 있습니다. 이 경우 Babel 같은 트랜스파일러를 사용해야 합니다.
async await와 generator 함수는 어떤 차이가 있나요?
generator 함수도 비동기 흐름 제어에 활용할 수 있지만, async await는 이를 단순화한 최신 문법입니다. 사용성과 직관성 면에서 async await가 더 뛰어납니다.

📌 async await를 활용한 비동기 처리 핵심 정리

async await는 자바스크립트 비동기 처리의 가독성과 유지보수성을 획기적으로 개선한 문법입니다.
Promise 기반으로 동작하면서도 동기 코드처럼 읽히기 때문에 초보자도 쉽게 접근할 수 있고, 실무에서는 API 통신, 데이터베이스 요청, 파일 업로드 등 다양한 영역에서 폭넓게 사용됩니다.
특히 try catch를 통한 에러 처리 방식은 명확하고 직관적이어서 예외 상황에서도 안정적인 코드를 작성할 수 있도록 돕습니다.

또한 여러 비동기 작업을 동시에 실행할 수 있는 Promise.all과의 조합, 특정 로직을 순차적으로 처리할 수 있는 await 활용 패턴은 실제 개발 생산성을 높이는 핵심 도구로 자리 잡았습니다.
즉, async await는 단순한 문법적 개선이 아니라, 자바스크립트 비동기 프로그래밍의 새로운 표준이라 해도 과언이 아닙니다.


🏷️ 관련 태그 : asyncawait, 비동기처리, 자바스크립트, Promise, trycatch, 에러처리, 프론트엔드개발, 웹개발, ES8문법, 코드가독성