본문 바로가기
javascript

ES8의 async와 await

by 초특급하품 2019. 10. 16.

이전 글(Promise)에서 비동기 작업을 구현하는 방법으로 콜백과 Promise에 대해서 알아봤다. javascript에서 피할 수 없는 비동기 작업을 구현하는데 초기에 콜백을 이용했고, 이를 개선하기 위해 ES6에 Promise가 등장했다.

 

Promise가 chaining을 통해 콜백의 많은 부족한 부분을 해결했지만, 역시 동기적인 코드가 갖는 직관적인 인상은 줄 수 없었다. 이처럼 아직 비동기 작업의 구현에는 갈증을 해소하지 못했고, 이를 채워줄 새로운 스펙이 ES8(ECMA2017)의 asyncawait이다.

 

async - await

요청 시점과 응답 시점이 달라서 구현하기 힘들었던 비동기 작업을 콜백, Promise로 제어했지만 역시 이상적으로는 아래처럼 동기적인 작업처럼 코딩하고 싶다.

function doAsyncJob() {
  const result1 = asyncJob1();
  const result2 = asyncJob2(result1);
  const result3 = asyncJob3(result2);
  return result3;
}

 

하지만 현실은 asyncJob*()의 결괏값은 작업 완료에 대한 결괏값이 아닐 것이고, 결괏값을 반환하게 한다면 함수가 blocking 될 거라 single-thread 환경에서 치명적일 것이다.

 

async, await는 현실과 이상을 아주 간단하게 연결시켜준다.

async function doAsyncJob() {
  const result1 = await asyncJob1();
  const result2 = await asyncJob2(result1);
  const result3 = await asyncJob3(result2);
  return result3;
}

 

하위 호환성을 보장하면서 새로운 기능이기 때문에 문법에 맞게 구현해야 하고, 그 문법은 상당히 간단하다.

 

첫 번째로 function 앞에 async를 붙이고, 두 번째로 비동기 작업(Promise를 리턴하는)을 하는 함수에 await을 붙이면 된다. 처음 보는 언어라면 이런 키워드구나 하고 넘기면 되겠지만, 기존에는 없다가 ES8에 추가된 기능인만큼 어떻게 구현이 되어 돌아가는지 이해하는 것은 중요하다.

 

동작 방법

어떻게 키워드 하나 붙였다고 실행 흐름이 멈추고, 그 비동기 작업이 완료되면 다시 실행되는 게 가능할까?

 

이 비슷한 작업을 이전 글(Iterator와 Generator)에서 Generator를 통해 구현한 적이 있다. yield로 값의 반환과 함께 실행 흐름이 멈추고, 나중에 next가 호출되었을 때 다시 이어서 진행된다.

 

이를 바탕으로 둘을 비교해서 풀어보면 아래 순서대로 진행된다.

1. async로 Generator 비슷하게 객체를 만들고

2. await으로 yield 비슷하게 실행 흐름을 멈췄다가

3. 비동기 작업의 완료 시점에 누군가 next를 호출해서 다시 실행

 

이렇게 비동기 작업을 두 개의 키워드로 동기식으로 바꿀 수 있다.

 

이를 일반화하기 위해서는 제약조건도 필요하다. await 다음에는 비동기 작업이 오는데 이는 Promise여야 한다.
이런 제약조건으로 항상 resolve또는 reject가 수행됨을 보장하기 때문에 살을 붙이기 용이하다.

async-await으로 선언된 구문을 적절히 기계적으로 Generator로 바꾸면 value가 Promise임을 이용해서 체이닝 할 수 있다.

function* doAsyncJobGenerator() {
  // asyncJob1, asyncJob2, asyncJob3 은 전부 Promise를 반환한다.
  const result1 = yield asyncJob1();
  const result2 = yield asyncJob2(result1);
  const result3 = yield asyncJob3(result2);
  return result3;
}

const asyncJobIterator = doAsyncJobGenerator();

asyncJobIterator.next().value.then(
    result1 => asyncJobIterator.next(result1).value
  ).then(
    result2 => asyncJobIterator.next(result2).value
  );

물론 실제 구현은 전달되는 데이터도 이렇게 간단하지 않을 거고, done도 확인하며 next를 호출할 거고, 에러도 핸들링하겠지만 기존의 방법을 이용해서 동기식으로 비동기 작업이 가능함을 보였다.

 

정리

새로운 스펙에 맞게 비동기 처리하는 키워드가 등장했다.
하지만 이전의 모든 장점은 안고, 단점은 보완하는 완벽한 방법은 없다.

동기식으로 구현하다 보니 Promise.all과 같이 독립적인 여러 개의 비동기 작업을 깔끔하게 표현할 수 없다.
이럴 때는 아래와 같이 Promise를 직접 만들어서 사용할 수밖에 없을 것 같다.

async function asyncMultiJob(){
  const promises = [asyncJob1, asyncJob2, asyncJob3];
  const results = await Promise.all(promises);
}

결국 언어가 진화하는 것에 맞게 그 이력들을 이해하고 따라가는 노력을 해야 한다.
다룰 수 있는 도구가 많아지면 요구조건에 맞는 가장 적절한 방법을 사용하기 편해질 것 같다.

'javascript' 카테고리의 다른 글

React class vs function component 차이점  (0) 2020.05.17
Typescript 타입 정의 파일  (0) 2020.03.29
CommonJS / ES 모듈 로딩 방식  (1) 2020.03.29
ES6의 Promise  (0) 2019.10.16
ES6의 Iterator와 Generator  (0) 2019.10.16

댓글