Javascript

[Async function] async/await 비동기 처리 :: 마이구미

mygumi 2018. 8. 22. 11:51
반응형

이 글은 ES8(ECMA2017) 스펙에서 정의된 async/await 키워드를 다뤄본다.

async/await 를 사용하기 위해서는 Promise 의 이해는 필요하다.

이 글에서는 다루지 않고, 오로지 async/await 에 초점을 맞춰 예제 위주로 진행한다.

실질적인 사용에 있어, 도움을 줄 수 있는 글이 될 것이라 생각한다.

참고한  - https://developers.google.com/web/fundamentals/primers/async-functions


ES8 에서 정의된 비동기 함수(async function) 는 용어 그대로, 비동기 처리를 위함이다.

async/await 를 사용하는 가장 큰 이유는 코드 품질의 향상이다.

코드를 읽기 좋게 해주고, 작성에 있어서도 굉장한 간결함을 나타낸다.


그렇다면, 새로운 기술이 아니라 그냥 키워드라고 보면 되는가?


그렇다.

앞에서 언급했듯이, Promise 의 이해가 필요하다고 했다.

그 이유는 async/await 는 Promise 를 기반으로 동작하기 때문이다.

그렇기에, Promise 에 익숙하거나 이해하고 온다면, 단순히 키워드 또는 패턴과 같은 형태라고 생각할 수 있다.

async/await 의 목적 중 한가지로 기존의 Promise 를 편하게 쓰기 위해서라고 볼 수 있다.




async/await 의 기본적인 사용법에 대한 예제는 다음과 같다.


function delay(item) { return new Promise(resolve => setTimeout(() => { console.log(item); resolve(); }, 500) ); } async function basic() { await delay(1); console.log("Done"); return "basic"; }


예제와 같이, async 키워드가 함수 앞에 정의되어야만, await 를 사용할 수 있다.

promise 를 await 하는 경우 promise 가 이행되어 값을 반환할때까지 함수는 일시적으로 중단된다.

결과적으로 1 을 먼저 출력한 후 "Done" 텍스트가 출력하게 된다.

결과적으로 비동기 처리를 async/await 를 사용함으로써, 다른 방법보다 훨씬 읽기 쉬운 코드로 작성할 수 있다.


async/await 는 어떻게 동작하는가?


async 함수는 await 사용 여부와 상관없이 항상 promise 를 반환한다.

해당 promise 는 async 함수가 반환하는 것과 함께 해결되거나 거부된다.

예를 들어, 위 예제에서는 "basic" 과 함께 이행된 promise 가 반환되는 것을 의미한다.


이것이 말하고자하는 의미는 async/await 없이 promise 를 사용한 예제를 통해 확인해보자.


function usePromise() { return Promise.resolve(1); // return Promise.reject("error"); } async function useAsync() { return 1; // throw "error"; }


2가지 함수는 똑같은 동작을 한다.

결국 언급했듯이, async/await 는 promise 기반이고, 목적 또한 promise 를 쉽게 쓰기 위함이라는 것을 생각하면 된다.

참고로 await 하는 것 또한 전부 Promise.resolve() 로 전달되기에, 안전하게 promise 를 await 할 수 있다.



이번에는 async/await 를 실질적으로 사용하는 예를 통해 주의할 점을 보자.

비동기 처리 또한, 루프를 사용하는 경우가 많이 존재한다.

배열의 각 요소에 대한 비동기 처리가 필요할 경우를 생각해보자.

우리는 배열의 실행 순서와 전체적인 함수의 흐름을 동기적으로 실행되기를 원한다.


async function loop1(array) { // array.map(async item => { // await delay(item); // }); array.forEach(async item => { await delay(item); }); console.log("Done!"); }


자바스크립트에 익숙하면 일반 for 문을 사용하지 않고, map 또는 forEach 문을 이용해서 코드를 작성할 것이다.

루프가 끝난 후, "Done!" 을 출력할 것을 기대한다.

하지만 이 방법은 기대와는 달리 다르게 동작한다.

직접 확인하기 위한 예제의 전체 코드는 다음과 같다.


function delay(item) { return new Promise(resolve => setTimeout(() => { console.log(item); resolve(); }, 500) ); } async function loop1(array) { array.forEach(async item => { await delay(item); }); console.log("Done!"); } loop1([1, 2, 3]);


출력값은 다음과 같다.


Done!

1

2

3


map, forEach 와 같은 메소드의 callback 을 async 함수로 선언함으로써, 각 배열의 요소를 await 한다.

호출하는 map, forEach 자체를 await 하지 않는다.

모든 배열 요소의 실행의 기다릴 필요없이 순서만 보장하면 되는 경우라면, 사용할 수 있다.

하지만 모든 경우에 대비하지 못하기 때문에, 좋은 코드는 아니라는 것을 알 수 있다.


해결하기 위한 방법은 map, forEach 대신 for 문을 사용하면 된다.

for 문이 마음에 들지 않는다면, for ... of 를 사용할 수 있다.


async function loop2(array) { for (const item of array) { await delay(item); } console.log("Done!"); }


=>

1

2

3

Done!


위와 같이 작성한다면, 우리가 원하는 결과를 출력할 수 있다.

하지만 이 코드 또한, 문제점을 짚어낼 수 있다.

바로 순차(직렬)적으로 처리된다는 것이다.

물론 그것이 원하는 흐름일 수도 있으나, 병렬적으로 처리해야하는 경우일 수도 있다.

병렬적으로 처리해도 된다면, 그렇게 하는것이 더 효율적일 것이다.

순차적으로 처리한다면, 예제는 500 * 3 = 1500 ms 가 걸리고, 병렬적이라면 최소 500 ms 걸리게 된다. (매번 병렬이 직렬보다 빠른 것은 아님)


병렬적으로 처리하기 위한 코드는 다음과 같다.


async function parallel(array) { const promises = array.map(item => delay(item)); await Promise.all(promises); console.log("Done!"); }


각 delay 함수를 병렬적으로 처리한 후, 모든 promise 가 완료될 때까지 await 한다.

실제 동작 순서를 확인한다면, 직렬과 병렬 처리에 대한 이해를 쉽게 할 수 있다.


  • 순차적 처리



  • 병렬적 처리




예제 코드

https://github.com/hotehrud/web-collection/blob/master/vanilla-javascript/async-await.js


반응형