자바스크립트의 Promise와 async/await 조금 더 이해해보기

제이미·2024년 12월 2일

자바스크립트

목록 보기
1/14
post-thumbnail

자바스크립트로 코드를 짜면서 내가 과연 비동기와 관련된 Promise 그리고 async/await을 이해하면서 쓰고 있는지에 대한 의문으로 시작된 글

나는 모든 프로젝트에 비동기 요청이 필요할 때마다 함수에서 async/await을 사용해왔다.
Promise를 사용해서 비동기 요청을 처리한 적은 거의 없는 듯 하다.

다시 비동기가 무엇인가부터 천천히 promise와 async/await까지 이해해보려고 한다!

자바스크립트에서 비동기(asynchronous)란?

: 특정 작업이 완료될 때까지 기다리지 않고, 다른 작업을 계속해서 수행하는 방식
(단일 스레드 기반인 자바스크립트에서 효율적으로 작업을 처리함)

여기서 잠깐,

그럼 동기와 비동기 작업을 어떻게 구분할까?

동기 함수는 호출 시 즉시 결과를 반환하면 반면에
비동기 작업 함수는 호출 즉시 결과를 반환하지 않고, 백그라운드 작업을 수행 또는 대기 후 결과를 반환하는 함수이다.

일반적으로 비동기 함수는

  • setTimout, setInterval 같은 시간 지연 타이머 함수
  • 브라우저의 네트워크 요청 함수 (fetch, AJAX, axios 등)
  • 파일 읽기/쓰기 (fs.readFile 같은)
  • 이벤트 리스너 (클릭, 입력 등)
  • Promise를 반환하는 함수
  • async/await을 사용하는 함수 (async 함수는 항상 Promise를 반환)

자바스크립트의 비동기 처리 방식은

: 이벤트 루프 (Event Loop)콜백 큐 (Callback Queue)를 이용하여 비동기 처리를 한다.

여기서 이벤트 루프
자바스크립트 엔진이 비동기 작업을 처리하는 핵심 메커니즘
콜 스택(Call Stack)에 작업이 없으면 콜백 큐(Callback Queue)에서 대기 중이었던 작업을 실행한다.
이벤트 루프, 콜백 큐, 콜 스택에 관련한 글은 다음 블로그에서 진행하겠음!

Promise와 async/await이란?

자바스크립트에서의 비동기 작업을 더 쉽게 다루기 위해 제공되는 기능이다.
두 가지 모두 비동기 코드 작성 시, 가독성을 높히고 콜백 지옥(Callback Hell)을 피하도록 도와준다.

Promise란?

: 비동기 작업의 상태, 결과를 표현하는 객체이다.

Promise는 3가지의 상태를 갖음!

  • Pending: 작업 진행 중인 상태
  • Fulfilled: 성공적으로 작업 완료 후 결과 반환한 상태
  • Rejecetd: 작업 실패 후 에러를 반환한 상태

Promise 문법을 확인해보자

Promise 객체는 callback 함수를 인자로 받는데, callback은 resolve와 reject를 인자로 받는다.

resolve는 작업이 성공적으로 완료된 후 반환될 값이 인자로 전달된다.
reject는 작업이 실패하였을 때의 메세지 같은 것들을 인자로 전달한다.

이 Promise를 반환하는 함수는 반환된 Promise 객체의 .then, .catch, .finally 등을 사용할 수 있다!
.then은 작업이 성공적으로 완료된 후 resolve에 전달되었던 값을 인자로 전달받아 원하는 로직으로 처리할 수 있다.
.catch는 작업이 실패하였을 때 reject에 전달되었던 메세지(또는 에러 메세지)를 인자로 받아 로그를 찍거나 다른 방식으로 처리할 수 있다.
.finally는 작업이 성공하거나 실패했을 때 상관없이 작업이 완료되면 실행되는 곳이다.

이로 인해, 콜백 지옥 문제를 해결하고 결과와 에러를 명확히 분리한다!

async/awiat이란?

: Promise를 더 간단하고 동기 코드처럼 보이게 작성할 수 있는 문법이다.

  • async: 함수가 항상 Promise를 반환
  • await: Promise 작업이 완료될 때까지 기다린 후 결과 반환

fetch로 네트워크 요청을 하는 async/await 함수 예시를 확인해보자!

위의 Promise를 사용할 떄는 함수를 구현한 후에 .then이나 .catch로 작업 완료 후의 로직을 짤 수 있었지만, async/awiat을 사용할 때는 try-catch문을 사용하여 함수 내에서 비동기 작업을 실행하며 에러 발생 시의 로직을 처리할 수 있다!
(fetch는 400이나 500대의 에러를 받아도 resolve로 처리하기에 이 함수에서는 !response.ok로 조건을 달아줌)

try 내에서는 실행될 작업의 로직이 포함되어 있고, await을 사용하여 비동기 작업 요청의 결과값을 받을 수 있다.
await 있으면, 비동기 작업이 완료된 후 순차적으로 그 밑의 로직들이 실행된다.

여기서 궁금한 건, 콜백 지옥 (Callback Hell)이란 무엇일까?

: 비동기 프로그래밍이 실행될 때 발생하며, 중첩된 콜백 함수가 지나치게 많아져 코드가 읽기 어렵고 관리하지 힘들어지는 상황을 의미

간단히 말하자면, 콜백 지옥은 비동기 작업을 연속적으로 수행하면서 만들어진다!

콜백 지옥이 되는 예시를 확인해보자

위의 예시를 보면, 코드의 가독성이 현저히 떨어지는 것을 확인할 수 있다.
이러한 상황을 피하기 위해 Promise를 사용하게 된 것이다.

async/await은 Promise를 좀더 간단하게 사용하기 위해 쓰이는 것이기에, async/await를 이용한 예시도 함께 확인해보자

위와 같이 await을 사용하여 비동기 작업을 순서대로 처리하여 받은 값으로 다음 작업을 수행한다!

Promise의 실행 작업을 사진으로 확인해보자

출처: https://www.alexlintu.com/

그럼 언제 Promise를 사용하고, async/await을 쓸까?

Promise는

  • 병렬 처리에 유용하다
    (Promise.all이나 Promise.race를 사용해 여러 비동기 작업을 병렬로 실행할 수 있다!)
  • 지원 범위가 넓다
    (오래된 코드 또는 라이브러리에서 자주 사용된다)

하지만,
코드가 복잡해질 수 있으며, 체인에서 에러 발생 시 추적하여 처리하기가 까다롭다.

async/await은

  • 가독성이 뛰어나다
    (동기 코드처럼 보인다)
  • try...catch를 사용해 모든 에러를 한 곳에서 관리할 수 있다

하지만.
await이 순차적 작업을 실행하여 병렬 작업이 필요하면 Promise.all를 추가 사용해야 하고 오래된 환경에서는 지원하지 않을 수 있어 트랜스파일링이 필요하다.

각각 장단점을 확인하고, 필요할 때의 상황을 잘 판단하여 비동기 작업을 실행해보자!



profile
프론트엔드 개발하다 궁금할 때

0개의 댓글