-
[Async function] async/await 비동기 처리 :: 마이구미Javascript 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
반응형'Javascript' 카테고리의 다른 글
removeEventListener, this 동작 :: 마이구미 (6) 2018.09.20 Javascript: hasOwnProperty 쓰는 이유 :: 마이구미 (5) 2018.08.31 Javascript:Shallow and Deep Copy :: 마이구미 (0) 2018.06.18 focusout, blur 이벤트 차이와 이해 :: 마이구미 (3) 2018.06.09 클로저(Closures) 무엇인가? :: 마이구미 (0) 2018.05.29