본문 바로가기
이론/Frontend

자바스크립트로 비동기 처리하기 : Async / Await

by 유세지 2023. 11. 9.

 

 

들어가며

지난 포스트인 자바스크립트로 비동기 처리하기 : Callback 과 Promise 와 이어지는 글입니다. 자바스크립트는 Callback에 이어 Promise라는 객체를 맞이하며 좀 더 편리하고 안전한 방식으로 비동기 작업을 처리할 수 있게 되었습니다.

 

이번 시간에는 비동기를 처리하는 또 다른 키워드인 async / await 에 대해 알아보겠습니다.

 

 

async / await

async / await은 ES8에 등장한 키워드로, 비동기 작업들을 마치 동기 작업을 처리하는 것과 비슷한 스타일로 보이도록 만들어준다는 특징이 있습니다. 가독성과 사용성을 높이기 위해 고안된 키워드인만큼, 사용 방법도 어렵지 않습니다. 비동기 작업을 감싸고 있는 함수에 async를, 해당 비동기 작업(promise 객체)에 await 키워드를 붙여주면 끝입니다.

 

지난 시간에 알아본 promise와 비교해볼까요?

 

 

// promise를 사용한 비동기 작업(fetch)
function promiseFunction() {
  return resultData = fetch('https://api.sampleapis.com/coffee/hot')
    .then((res) => res.json())
    .catch((error) => {
      console.error(error);
    });
}

promiseFunction().then((data) => {
  console.log(data);
});

// (20) [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]

 

 

이 코드를 async / await 을 사용한 코드로 바꾸면 이렇게 됩니다.

 

 

// async await을 사용한 비동기 작업(fetch)
async function asyncFunction() {
  try {
    const resultData = await fetch('https://api.sampleapis.com/coffee/hot');
    return resultData.json();
  } catch(error) {
    console.log(error);
  }
}

console.log(await asyncFunction());

// (20) [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]

 

 

promise를 사용하여 then chaining의 형태를 띄던 모습에서,

await 키워드를 붙여 일반적인 동기식 코드의 외형으로 바뀌었습니다.

 

코드에서도 나타나는 것처럼, async / await 방식은 try ~ catch 문을 이용해 에러 핸들링이 가능합니다.

기존의 코드 흐름과 유사하여 promise보다 알아보기 쉬운 구조가 되었습니다.

 

다만 promise의 경우에도 뎁스가 깊지 않다면 가독성을 크게 해치지 않아 섞어서 사용하는 경우도 있습니다.

다음은 두 가지를 섞어 사용하는 경우입니다.

 

 

// async await와 promise를 함께 사용한 비동기 작업(fetch)
async function asyncFunction() {
  return resultData = await fetch('https://api.sampleapis.com/coffee/hot')
    .then((data) => data.json())
    .catch((error) => { 
      console.log(error);
    });
}

console.log(await asyncFunction());

// (20) [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]

 

 

 

이렇게 async / await 키워드를 사용하면 promise 객체를 더 잘 다룰 수 있게 됩니다.

정리하면, promise 객체는 이러한 흐름을 따릅니다.

 

 

async await을 사용한 Promise 객체의 흐름

 

 

 

pending을 거쳐 fulfilled과 rejected의 결과로 나오는 값들을 각각 then과 catch로 받던 것을 await 키워드를 통해 바로 얻을 수 있는 모습입니다. promise의 상태가 fulfilled라면 data를 받을 것이고, rejected의 경우 error가 throw 됩니다.

 

추가로 ES2022부터는 Top-level await가 추가되어 굳이 async로 감싸지 않아도 모듈의 최상단에서 await 키워드를 쓸 수 있게 되었습니다. 타입스크립트를 사용하신다면, tsconfig 파일에서 target 속성을 es2017이나 그 이상으로 설정해주시면 오류없이 사용 가능합니다.

 

 

 

Generator

제너레이터는 순서에 따른 동작들을 담는 데이터 구조입니다. 흔히 이터러블이면서 이터레이터라고 하는 규약을 준수하는 객체를 제너레이터 객체라고 부릅니다. 제너레이터에 대한 자세한 설명은 이후 별도의 포스팅으로 남기겠지만, 지금은 순회를 가능하게 해주는 메서드자신의 다음을 가리키는 프로퍼티가 구현된 객체라고 생각해주시면 충분합니다.

 

여기서 제너레이터 이야기가 나온 이유는, async await 키워드가 내부에서 제너레이터를 이용하여 구현되고 있기 때문입니다. await 키워드 뒤에 온 promise 객체는 코드에서 호출된 순서를 보장하며 실행됩니다. 이러한 구현이 가능하려면, 비동기 작업이 스스로 자신 이후에 어떤 비동기 작업이 와야하는지 알아야하고, 전체 작업 흐름에서도 일관되게 유지되어야합니다.

 

이러한 관점에서 제너레이터는, 연속되는 비동기 동작들을 담기에 매우 적절하다고 볼 수 있겠습니다.

실제로, v8 레포지토리에 가면 await 메서드로 받은 동작을 generator에 담아 처리하는 것을 확인할 수 있습니다.

 

 

V8의 Await 구현 일부

 

 

 

마치며

두 포스트에 걸쳐 callback과 promise, async / await까지 살펴보았습니다.

자바스크립트에서의 비동기 작업은 선배 개발자들이 어떤 불편을 겪었고, 이를 해결하기 위해 어떤 노력을 거쳤는지 한 눈에 알 수 있는 대표적인 대목이 아닐까 싶습니다.

 

틀린 내용이나 부족한 부분은 댓글로 남겨주시면 감사하겠습니다.

 

읽어주셔서 감사합니다.

 

 

 

참고 문서

 

 

반응형

댓글