Logo

[자바스크립트] 비동기 처리 3부 - async/await

이전 두 개의 포스팅를 통해서 기존에 자바스크립트로 어떻게 비동기 처리 코드를 작성해왔는지에 대해서 살펴보았습니다. 이번 포스팅에서는 좀 더 개선된 방식으로 비동기 처리를 할 수있도록 도와주는 async/await에 대해서 알아보도록 하겠습니다. async/await를 제대로 시용하려면 Callback과 Promise에 대한 이해가 무엇보다 중요하오니 아래 포스팅도 참고 바라겠습니다.

Promise를 통한 비동기 코딩

먼저 Promise를 통해서 어떻게 비동기 처리를 하는지 간단하게 리뷰해보겠습니다. 어떤 원격 REST API를 호출을 하여 게시물 작성자의 이름을 리턴하는 함수를 작성하고 그 함수를 호출해보았습니다.

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()를 호출하여 Promise 객체를 리턴받은 후에, Method Chaning 기법을 통해 then() 메서드를 연쇄적으로 호출하고 있습니다. 마치 리눅스의 파이프(|) 키워드처럼 then() 메서드는 바로 이전 then() 메서드의 출력값을 입력값으로 사용하여 새로운 출력값을 만들고, 바로 다음 then() 메서드의 입력값으로 넘겨줍니다.

fetchAuthorName() 함수 자체도 결국 게시물 작성자의 이름을 얻을 수 있는 Promsie 객체를 리턴하기 때문에, 호출할 때도 then() 메서드를 사용하여 게시물 작성자의 이름을 출력해야 합니다.

문제점

Promise를 사용하면 자연스럽게 위와 같은 코딩 스타일로 비동기 처리를 하게되는데 여기에는 여러 가지 문제점이 있습니다.

  1. 디버깅

위 코드의 8번째 줄을 아래처럼 에러가 발생하도록 의도적으로 수정 후에 코드를 실행을 해보면 다음과 같은 에러 메시지를 보게 됩니다.

.then(user => user1.name);
  • 결과
ReferenceError: user1 is not defined
    at fetch.then.then.then.then.then (<anonymous>:7:29)

동일한 이름의 메서드인 then()을 연쇄적으로 호출하고 있어서 도대체 몇 번째 then()에서 문제가 발생한 건지 Stack Trace을 보더라도 혼란스러울 수 있습니다.

또한 then() 메서드 호출 부에 break point를 걸고 디버거를 돌리면, 위 코드와 같이 화살표 함수로 한 줄짜리 콜백 함수를 넘긴 경우에는 코드 실행이 break point에서 멈추지 않기 때문에 디버깅이 상당히 불편합니다.

  1. 예외 처리

Promise를 사용하면 try/catch 대신에 catch() 메서드를 사용하여 예외 처리를 해야합니다. 이 부분이 비동기 코드만 있을 때는 그렇게 거슬리지 않는데, 동기 코드와 비동기 코드가 섞여 있을 경우 예외 처리가 난해해지거나 예외 처리를 누락하는 경우가 생기기 쉽습니다.

  1. 들여쓰기

실제 프로젝트에서는 샘플 코드와 같이 간단한 구조가 아닌 복잡한 구조의 비동기 처리 코드를 작성하게 됩니다. 따라서, then() 메서드의 인자로 넘기는 콜백 함수 내에서 조건문이나 반복문을 사용하거나 여러 개의 Promise를 병렬로 또는 중첩해서 호출해야하는 경우들이 발생하게 됩니다. 이럴 경우, 다단계 들여쓰기를 해야할 확률이 높아지며 코드 가독성은 점점 떨어지게 됩니다.

async/await 키워드를 통한 비동기 코딩

Promise의 이러한 불편한 점들을 해결하기 위해 ES7(ES2017)에서 async/await 키워드가 추가되었습니다. 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));
  • 출력
name: Leanne Graham

달라진 점을 찾아 보면 먼저 함수 선언부를 보면 async 키워드가 function 앞에 붙었다는 것을 알 수 있습니다. 그리고 Promise를 리턴하는 모든 비동기 함수 호출부 앞에는 await 키워드가 추가되었습니다.

await 키워드는 async 키워드가 붙어있는 함수 내부에서만 사용할 수 있으며 비동기 함수가 리턴하는 Promise로 부터 결과값을 추출해줍니다. 즉, await 키워드를 사용하면 일반 비동기 처리처럼 바로 실행이 다음 라인으로 넘어가는 것이 아니라 결과값을 얻을 수 있을 때까지 기다려줍니다. 따라서 일반적인 동기 코드 처리와 동일한 흐름으로 (함수 호출 후 결과값을 변수에 할당하는 식으로) 코드를 작성할 수 있으며, 따라서 코드를 읽기도 한결 수월해집니다.

한가지 주의할 점은 async 키워드가 붙어있는 함수를 호출하면 명시적으로 Promise 객체를 생성하여 리턴하지 않아도 Promise 객체가 리턴됩니다. 따라서 호출부를 보시면 Promise 객체를 사용했던 것 동일한 방식으로 then() 메서드를 통해서 결과값을 출력하고 있습니다.

하지만 만약 이 호출부가 또 다른 async 키워드가 붙어있는 함수의 내부에 있다면 동일한 방식으로 await 키워드를 사용하여 Promise가 제공할 값에 바로 접근할 수 있습니다.

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

printAuthorName(1);

예외 처리

동기/비동기 구분없이 try/catch로 일관되게 예외 처리를 할 수 있는 부분도 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;

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

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

마치면서

이상으로 async/await를 이용해서 좀 더 깔끔하게 비동기 코딩을 하는 방법에 대해서 알아보았습니다. 앞으로 가면 갈수록 async/await를 사용한 자바스크립트 코드를 볼 일이 많아질거라 생각합니다. 이 번 기회에 async/await를 마스터하시고 적극적으로 활용해보셨으면 좋겠습니다.

전체 코드

https://gist.github.com/DaleSeo/0c84a7361d55311b10558bd55e25f2e2