글 목록

최신 글과 검색 결과
DEVELOPMENT

Async/Await 문법 알아보기

간지뽕빨리턴님

이 글의 목차

    반응형

    "처음 접하면 더 헷갈리는 문법, async/await을 제대로 정리해 봅니다."

    자주 쓰지만 의외로 헷갈리는 Async / Await

    개발을 하다 보면 자주 사용하는 문법 중 하나인 async와 await의 개념을 정리해 보려 합니다. 자바스크립트를 기준으로 비동기 처리가 왜 필요한지부터, async/await의 동작 원리와 실제 예제, 그리고 여러 비동기 작업을 동시에 처리하는 방법까지 차례로 다루겠습니다. 보시다가 보완할 점이 있으면 댓글로 남겨 주세요.

    목차

      async/await이란

      async/await은 비동기 프로그래밍을 위한 구문으로, 프로미스(Promise)를 더 간결하고 직관적으로 다룰 수 있게 해 줍니다. 콜백 지옥과 길게 이어지는 .then() 체인을, 마치 동기 코드를 읽듯 위에서 아래로 읽히는 형태로 바꿔 주는 것이 핵심입니다. 기본 개념은 대부분의 언어에서 비슷하지만 언어별로 세부 동작은 조금씩 다를 수 있으니, 큰 그림 위주로 이해하시면 됩니다.

      비동기 처리가 필요한 이유

      먼저 비동기 처리가 왜 필요한지부터 짚어 보겠습니다. 크게 세 가지 이유로 정리할 수 있습니다.

      1. 시간이 걸리는 I/O 작업

      네트워크 요청, 파일 시스템 접근, 데이터베이스 조회 같은 작업은 완료까지 시간이 걸립니다. 이를 동기적으로 처리하면 작업이 끝날 때까지 프로그램 전체가 멈추기 때문에, 사용자는 화면이 느리거나 멈췄다고 느끼게 됩니다.

      2. 사용자 응답성 유지

      비동기로 처리하면 오래 걸리는 작업이 진행되는 동안에도 화면이 멈추지 않습니다. 사용자는 그동안 다른 조작을 계속할 수 있어, 매끄러운 사용 경험을 유지할 수 있습니다.

      3. 서버 동시성 처리

      여러 요청을 동시에 처리해야 하는 서버 환경에서는 비동기 방식이 적합합니다. 작업을 기다리는 동안 CPU나 메모리를 붙잡고 있지 않으므로, 자원을 효율적으로 쓰며 더 많은 요청을 감당할 수 있습니다.

      단점도 있습니다

      비동기 처리에도 단점은 있어, 필요한 상황에서 적절히 사용해야 합니다. 대표적인 단점은 다음과 같습니다.

      1. 코드 복잡성 증가

      콜백, 프로미스 체인, async/await 등을 섞어 쓰다 보면 코드 흐름이 복잡해져 유지보수와 디버깅이 어려워질 수 있습니다.

      2. 에러 추적·디버깅의 어려움

      여러 비동기 작업이 얽히면 어느 지점에서 에러가 발생했는지 파악하기가 상대적으로 어렵습니다. 실행 순서가 고정되어 있지 않아 흐름이 명확하지 않으므로, 원인을 찾는 데 더 많은 시간이 들 수 있습니다.

      3. 메모리 누수 위험

      어떤 오류나 문제로 비동기 작업이 완료되지도 취소되지도 않은 채 남으면, 메모리 누수가 발생할 수 있습니다.

      async와 await, 각각의 역할

      async는 함수를 비동기 함수로 선언하는 키워드입니다. async가 붙은 함수는 항상 프로미스를 반환하며, 프로미스가 아닌 값을 반환하더라도 그 값을 이행(resolved) 상태의 프로미스로 감싸서 돌려줍니다. await은 이 async 함수 내부에서만 사용할 수 있으며, 프로미스가 처리될 때까지 그 줄에서 기다렸다가 결과 값을 꺼내 줍니다. 핵심만 추리면 다음 두 가지입니다.

      • async 함수는 항상 프로미스를 반환한다.
      • await은 async 함수 내부에서만 사용할 수 있다.

      .then() 체인과 비교해 보기

      async/await의 장점은 같은 로직을 프로미스 체인으로 작성해 보면 분명해집니다. 아래 두 코드는 동일하게 동작하지만, 가독성에서 차이가 납니다.

      // .then() 체인 방식
      function searchData() {
          return fetch('test')
              .then(response => response.json())
              .then(data => data);
      }
      
      // async/await 방식 (위와 동일한 동작)
      async function searchData() {
          const response = await fetch('test');
          const data = await response.json();
          return data;
      }

      await을 쓰면 비동기 코드가 마치 위에서 아래로 읽는 동기 코드처럼 보여, 흐름을 따라가기가 훨씬 수월합니다.

      기본 문법과 예제

      /*
      * 비동기 처리를 위한 async 기본 문법
      */
      async function functionAsync() {
          // 수행할 코드
      }

      async 함수의 기본 형태입니다. 함수 안에서 수행할 동작을 작성하면 됩니다. 이제 fetch로 데이터를 가져오는 실제 예제와, 에러 처리를 더한 버전을 보겠습니다.

      async function searchData() {
          const response = await fetch('test');
          const data = await response.json();
          return data;
      }
      
      searchData().then(data => { console.log("수신 결과 : ", data); });
      
      
      // 에러 처리까지 해봅시다.
      async function searchData() {
          try {
              const response = await fetch('test');
              const data = await response.json();
              return data;
          } catch (error) {
              console.log("에러 발생 : ", error);
          }
      }
      
      searchData();

      예시로 searchData라는 함수를 만들고, response 변수에 fetch로 데이터를 가져옵니다. await을 사용했기 때문에 fetch와 json의 결과를 기다린 뒤, 반환된 프로미스가 처리되면 then에서 데이터를 출력합니다. 에러 처리는 try...catch로 감싸면 되는데, 동기 코드처럼 직관적으로 예외를 잡을 수 있다는 점이 async/await의 또 다른 장점입니다.

      여러 작업 동시에 처리하기 (Promise.all)

      여러 비동기 작업을 처리할 때, await을 한 줄씩 나열하면 앞 작업이 끝나야 다음이 시작되어 느려집니다. 서로 의존하지 않는 작업이라면 Promise.all로 한 번에 병렬 처리하는 것이 효율적입니다.

      // 느린 방식: 순차 실행 (A가 끝나야 B 시작)
      const a = await fetchA();
      const b = await fetchB();
      
      // 빠른 방식: 병렬 실행 (A, B 동시에 시작)
      const [a, b] = await Promise.all([fetchA(), fetchB()]);

      진행 중인 요청 취소하기 (AbortController)

      진행 중인 비동기 요청을 중간에 취소하고 싶을 때는 AbortController를 사용합니다. controller의 signal을 fetch에 전달한 뒤, controller.abort()를 호출하면 요청이 중단됩니다.

      const controller = new AbortController();
      
      fetch('test', { signal: controller.signal })
          .then(res => res.json())
          .catch(err => {
              if (err.name === 'AbortError') console.log('요청이 취소되었습니다.');
          });
      
      // 필요한 시점에 요청 취소
      controller.abort();

      이 글과 함께 보면 좋은 글로, 비동기의 기초가 되는 프로미스를 정리한 글을 추천합니다.

      ▶ 내가 이해하기 쉽게 정리하는 프로미스(Promise) 알아보기 [바로가기]

      마무리

      지금까지 async/await의 개념과 활용법을 살펴봤습니다. 비동기 처리가 필요한 이유부터, async와 await의 역할, .then() 체인과의 비교, 그리고 Promise.all과 AbortController까지 함께 정리했습니다. 위에서 추천한 프로미스 글을 함께 보시면 이해가 한층 깊어질 것입니다. 궁금한 점이 있으면 댓글로 남겨 주세요. 확인하는 대로 답변드리겠습니다.