Javascript/Javascript

[Javascript] 비동기 처리 - (3) async/await

금요일인줄 2025. 7. 3. 09:00
728x90
반응형

 

 

 

async/await에 대해 알아보기 전에 먼저 자바스크립트에서 Promise를 이용해 비동기 처리를 어떻게 해왔는지 간단히 살펴 보자.

 

예를 들어 특정 게시물의 작성자 이름을 가져오는 함수는 다음과 같이 작성할 수 있다.

function fetchAuthorName(postId) {
  return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
    .then((response) => response.json())
    .then((post) => post.userId)
    .then((userId) => {
      return fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
        .then((response) => response.json())
        .then((user) => user.name);
    });
}

fetchAuthorName(1).then((name) => console.log("name:", name));

// 출력 결과
// name: Leanne Graham

 

이 함수는 fetch()를 이용해 게시물을 불러오고 그 안에서 userId를 추출한 다음 다시 해당 사용자의 정보를 요청해서 name을 반환하는 구조이다. 중간중간 필요한 데이터를 .then() 체이닝을 ㅌ통해 단계별로 받아 처리하고 있고 최종적으로 작성자의 이름을 얻을 수 있다.

 

이처럼 프로미스는 마치 리눅스의 파이프(|)처럼 이전 단계의 출력값을 다음 단계의 입력값으로 전달하면서 비동기 흐름을 구성한다. 결과적으로 fetchAuthorName() 함수는 작성자 이름을 비동기적으로 리턴하는 Promise 객체이므로 호출할 때도 .then()을 사용해 결과를 받아야 한다.

 

 

Promise 방식의 한계

하지만 .then()을 계속 이어붙이는 방식은 복잡한 로직을 작성할수록 몇 가지 문제를 낳는다.

 

  1. 디버깅이 불편하다
    예를 들오 .then(user => user1.name)처럼 오타가 나면 에러 메시지에 어떤 .then()에서 문제가 발생했는지 명확하게 드러나지 않아 추적이 어려울 수 있다. 특히 화살표 함수로 한 줄짜리 콜백을 작성한 경우 브레이크 포인트를 걸어도 디버거가 멈추지 않아 디버깅이 번거롭다.
  2. 예외처리 흐름이 직관적이지 않다.
    Promise에서는 try/catch 대신 .catch()로 예외처리를 해야 한다. 이 자체는 문제가 되지 않지만 동기 코드와 비동기 코드가 섞여 있는 경우에는 예외처리가 끊기거나 누락되기 쉬워진다.
  3. 가독성이 떨어진다
    단순한 작업일 때는 체이닝 구조가 괜찮지만 실제 프로젝트에서는 조건문, 반복문, 중첩 호출, 병렬 처리 등 다양한 로직이 섞이기 마련이다. 그러다 보면 .then()안에 또 .then()이 들어가는 식으로 들여쓰기가 깊어지고 결국 코드의 가독성이 급격히 나빠진다.

이러한 이유들로 인해 자바스크립트에서는 더 읽기 쉬운 비동기 코드의 작성을 목적으로 async/await 문법이 도입되었다.

 

 


 

async/await 키워드를 이용한 비동기 처리

앞서 살펴본 것처럼 Promise를 활용하면 비동기 처리를 체계적으로 할 수 있지만 .then() 체이닝 방식은 디버깅이 불편하고 가독성이 떨어지며 예외 처리도 다소 번거로울 수 있다.

 

이러한 단점을 개선하기 위해 ES2017(ES8)에서는 async/await 키워드가 도입되었다. 이 문법을 사용하면 비동기 코드를 마치 동기 코드처럼 자연스럽게 작성할 수 있다.

 

위 예시를 async/await으로 바꿔 보면 아래와 같다.

async function fetchAuthorName(postId) {
  const postResponse = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${postId}`
  );
  const post = await postResponse.json();
  const userId = post.userId;

  const userResponse = await fetch(
    `https://jsonplaceholder.typicode.com/users/${userId}`
  );
  const user = await userResponse.json();

  return user.name;
}

fetchAuthorName(1).then((name) => console.log("name:", name));

 

변화된 점을 살펴 보면

  • 함수 앞에 async 키워드가 붙었다.
  • 모든 비동기 작업(fetch, json 파싱 등) 앞에 await 키워드가 붙었다.
  • 중첩된 .then() 없이 일반적인 동기 코드처럼 순서대로 코드를 작성할 수 있다.

await는 Promise가 완료될 때까지 기다린 뒤 그 결과를 변수에 담아 다음 라인으로 넘어간다. 덕분에 코드 흐름이 훨씬 직관적이고 읽기도 훨씬 편해졌다.

 

 

async 키워드가 붙은 함수는 자동으로 Promise 객체를 반환한다. 따라서 fetchAuthorName() 함수는 명시적으로 new Promise()를 사용하지는 않았지만 결과적으로는 프로미스를 반환하므로 호출 시 .then()을 통해 결과를 처리할 수도 있다. 그리고 만약 호출부토 async 함수라면 .then() 대신 await을 사용해서 바로 값을 받을 수 있다.

async function printAuthorName(postId) {
  const name = await fetchAuthorName(postId);
  console.log("name:", name);
}

printAuthorName(1);

 

 

async/await의 또 하나의 장점은 예외 처리를 try/catch 블록으로 동기 코드와 동일한 방식으로 할 수 있다는 점이다. .catch()로 따로 이어붙이지 않아도 되고 동기/비동기 코드가 섞여 있어도 일관된 방식으로 처리할 수 있다.

async function fetchAuthorName(postId) {
  const postResponse = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${postId}`
  );
  const post = await postResponse.json();
  const userId = post.userId;

  try {
    const userResponse = await fetch(
      `https://jsonplaceholder.typicode.com/users/${userId}`
    );
    const user = await userResponse.json();
    return user.name;
  } catch (err) {
    console.log("Failed to fetch user:", err);
    return "Unknown";
  }
}

fetchAuthorName(1).then((name) => console.log("name:", name));

 

위 코드에서는 사용자 정보를 가져오는 과정에서 문제가 생길 경우 "Unknown"이라는 기본 값을 반환하도록 처리했다. 이처럼 에러 발생 가능성이 있는 부분만 try/catch로 감싸면 되므로 코드 구조 또한 훨씬 깔끔해진다.

 

 

async/await은 자바스크립트에서 비동기 코드를 더 깔끔하고 직관적으로 만들 수 있는 강력한 도구이다. 특히 Promise 체이닝 방식의 한계로 인해 코드가 난잡해질 때 async/await을 도입하면 읽기 쉬우면서 일관된 예외처리, 편리한 디버깅이라는 세 마리 토끼를 한꺼번에 잡을 수 있다.

 

 

 

 

 

 

 

 

 

728x90
반응형