Promise 정리: async/await 사용법 & then과의 차이

2021. 4. 11. 18:31프론트엔드 개발/JS & TS

서비스 개발을 하다보면 비동기적으로 개발해야 할 일이 많습니다. JavaScript에서는 과거 callback 함수를 통해 비동기를 구현하곤 했으나 요즘에는 Promise 객체를 반환하게 하여 async와 await로 작업이 완료되면 다음 로직이 진행되게끔 지연시키는 방식을 통해 비동기를 구현하곤 합니다. 

 

최근 회사에서 앱을 개발하면서 한번 사용하는 방법을 간단하게 정리해봤습니다. (Promise 객체 자체에 대해서는 developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Using_promises 이 링크를 참고하세요.)


Promise 객체를 반환하게끔 작성한 함수를 호출할 땐 함수명 앞에 await 키워드를 붙이고 호출하면 해당 작업이 완료되고 나서야 이후 코드가 진행됩니다.

 

의사코드로 간단히 살펴봅시다.

// "샘플함수"는 비동기 작업 함수를 호출하여 콘솔에 기록하는 함수

샘플함수 {
  const 작업결과 = 비동기 작업 함수 실행 // 시간이 걸림
  console.log(작업결과)
}

위 작업을 비동기처리 없이 구현하면 console.log(작업결과)undefined를 출력합니다.

즉, 비동기 작업을 수행하는 함수가 결과값을 반환해 '작업결과'에 할당을 하기도 전에 console.log 함수가 실행되었기 때문이죠.

 

이렇게 Promise를 반환하는 함수 반환값을 사용하기 까지 코드를 지연시키기 위해 async, await를 사용합니다.

async와 await를 활용한 방식

위 의사코드를 await와 async 키워드와 함께 실제 코드로 변경해봅시다.

const sampleFunc = async () => {
    const result = await asyncFunc() // asyncFunc 함수는 Promise 객체를 반환한다
    console.log(result)
}

Promise 객체를 반환하는 함수 asyncFunc 앞에 await를 붙이고, 이를 포함하는 함수 앞에 async를 붙여줍니다. 여기서는 sampleFunc에 할당하는 익명함수가 되겠습니다. await 키워드를 쓰려면 반드시 async 키워드로 선언된 함수 내부여야 합니다.

 

이렇게 하면 result가 제대로 출력됩니다. asyncFunc함수의 실행결과가 반환되어 result에 할당되기 전까지, await 키워드가 console.log 함수의 실행을 지연시켰음을 알 수 있습니다.

async/await에서 예외처리

위 코드에서 예외처리는 다음과 같이 할 수 있다.

const sampleFunc = async () => {
    try {
        const result = await asyncFunc() // asyncFunc 함수는 Promise 객체를 반환한다
        console.log(result)
    } catch (err) {
        console.log(err)
    }
}

만약 asyncFunc에서 reject를 뿜어냈을 경우 catch에서 해당 내용을 잡아 콘솔에 찍어냅니다.


(참고) then을 활용한 방식

지금까지 async, await를 이용한 방식을 보셨는데요, 사실 이런 방식 이전에 then을 사용하는 방식이 있었습니다. 위의 코드를 then으로 구현하면 다음과 같습니다.

const sampleFunc = () => {
    asyncFunc() // asyncFunc 함수는 Promise 객체를 반환한다
          .then(result => console.log(result))
}

얼핏 보면 코드가 깔끔해보이지만, 사실 depth가 한 단계 더 들어가면서 콜백지옥의 전초처럼 보이기도 한다.

지금은 매우 간단한 함수여서 그리 복잡해 보이지 않지만, 실제 코드를 다룰 때 then을 활용한 방식은 가독성이 떨어질 수 있습니다. 콜백지옥에서 벗어나기 위해 Promise가 도입되었는데, 콜백지옥같은 들여쓰기 지옥(Tab Hell)을 맛보게 될 수도 있습니다.

then 예외처리

then을 활용한 방식에서의 예외처리는 다음과 같습니다.

const sampleFunc = () => {
    asyncFunc() // asyncFunc 함수는 Promise 객체를 반환한다
        .then(result => console.log(result))
		.catch(err => console.log(err))

// 이런 식으로도 가능하다.
const sampleFunc2 = () => {
    asyncFunc() // asyncFunc 함수는 Promise 객체를 반환한다
		.then(
			result => console.log(result),
			err => console.log(err)
        )
}

이미 알았겠지만 예외처리 과정에서도 await, async를 사용하는 것이 가독성에서도 좋고, 이해하기에도 편합니다.

 

그럼 then은 쓰지 말아야 하는 걸까요? 그렇진 않습니다. then은 이러한 비동기가 연속적으로 일어나는 상황에서 각 Promise객체마다 다른 로직으로, 체이닝을 구성하려고 할때 더 유용하게 사용할 수 있습니다. 여기에 관련해선 Promise.prototype.then()#체이닝을 참고해주세요.


async와 await를 활용한 비동기 구현에 대해 알아봤습니다. 지금은 Promise 객체 하나를 다루는 비동기를 구성했는데, 만약 여러 Promise 객체를 병렬적으로 처리하려고 한다면 어떻게 해야 할까요? 가령 [ Promise 객체, Promise 객체, ... ]라면 각각의 Promise 객체들이 모두 결과를 반환한 뒤에 이후 로직을 처리해야 할 일이 있습니다. 이 때는 Promise.all 을 활용하여 해결할 수 있습니다.

 

다음에는 Promise.all에 대해 알아보겠습니다.

 


이 글이 도움이 되셨다면 바로 아래의 좋아요(공감)❤️ 부탁드려요.

 

내용 중 오류가 있거나 보충해주실 내용이 있으면 댓글을 통해 알려주시면 바로 반영하겠습니다. 감사합니다.