Javascript에서 async/await의 병목 문제 개선하기

Alvin
6 min readMay 17, 2020

--

개요

Javascript의 async/await는 비동기 처리를 콜백의 맞물림 없이 깔끔하게 처리할 수 있도록 도와주는 문법입니다. 동일한 역할을 하는 Promise보다 코드가 깔끔해지고, 직관적이기에 저는 Promise보다 async/await를 선호합니다.

하지만 async/await를 남용하면 느리지 않아도 될 작업을 느리게 만들 수 있습니다. 이번 글에서는 async/await를 남용했을 때 생기는 병목 현상을 개선해보겠습니다.

첫 번째 예시

Promise를 반환하는 async 함수 a, b, c를 선언했습니다. 각각의 Promise는 1초 후 resolve 됩니다.

함수 a, b, c를 실행하는 main 함수입니다. console.time(), console.timeEnd()는 main 함수의 실행시간을 측정하기 위해서 작성하였습니다. 이제 위처럼 await를 단순 나열하는 것이 어떤 결과를 불러일으키는지 확인해봅시다.

위는 실행 결과를 캡쳐한 사진입니다. 분명히 함수 a, b, c가 return하는 각각의 Promise들은 1초 후 resolve되고, async/await는 비동기 처리 문법이라고 했습니다. 그런데 모든 Promise가 resolve되고 main이 종료되기까지 무려 3초의 시간이 걸렸습니다. 어째서일까요?

그 이유는 바로 async/await는 코드가 동기적으로 실행되도록 만들기 때문입니다. 따라서 실행 시간이 불규칙적인 비동기 작업을 개발자가 의도한 순서대로 동작시킬 수 있는 것입니다. 하지만 잘못 사용했더니 ‘직관성’은 얻었지만 ‘속도’를 잃었습니다.

그러나 아직 슬퍼하기엔 이릅니다. 우리에겐 Promise.all이라는 친구가 있습니다.

Promise.all

Promise.all은 인자로 Promise 배열을 받고, 병렬로 처리합니다.

main 함수를 수정했습니다. await를 단순 나열하는 것 대신 Promise.all을 사용하도록 하였습니다. 아래는 그 결과입니다.

위에서 봤던 결과와 다르게 main 함수가 1초만에 종료되었습니다. 각각의 Promise가 병렬로 처리되었기 때문입니다. 함수 a, b, c의 Promise가 서로 의존적이었다면 Promise.all을 사용하면 안되지만, 서로 독립적이었기 때문에 병렬로 처리해서 실행 시간을 단축할 수 있었습니다.

이제 async/await의 모든 병목을 해결할 수 있을까요? 아닙니다. 또 다른 예시를 살펴봅시다.

두 번째 예시

위에서 작성한 함수 a, b, c를 수정해줍시다.

함수 a의 Promise는 2초 후 resolve됩니다. 함수 b의 Promise는 1.5초 후 resolve되고 결과값으로 1000을 넘깁니다. 함수 c의 Promise는 n초 후 resolve됩니다.

main 함수도 수정해줍시다. 함수 a, b의 Promise는 동시에 실행됩니다. 그리고 b의 결과를 받아서 c를 실행합니다. 함수 b의 Promise의 결과가 1000이므로, 함수 c의 Promise는 1초 후 resolve됩니다.

실행 결과입니다. 3초가 걸렸지만, 더 빠르게 끝낼 수도 있습니다. main 함수를 자세히 살펴봅시다.

함수 a의 Promise는 2초 후 resolve됩니다. 그러나 함수 b의 Promise는 1.5초 후 resolve 되기 때문에 a의 Promise가 끝날 때 까지 기다리게 됩니다. 그 후 c가 실행되고, 1초 후에 resolve되어 총 실행시간이 3초가 되는 것입니다.

위 코드의 문제점은 c는 b의 결과를 받지만, a가 끝날때 까지 기다린다는 것입니다. 이런 경우 b와 c를 먼저 처리해줌으로써 실행시간을 단축할 수 있습니다.

수정한 main 함수와 그 결과입니다. a의 Promise보다 b의 Promise를 먼저 await함으로써 c를 빨리 처리할 수 있게 되었고, 실행시간이 단축되었습니다.

마무리

이번 글에서는 async/await의 병목 현상을 알아보고, 개선해보았습니다. 사실 저도 얼마전까진 async/await의 병목에 대해서 몰랐습니다. 아니, 몰랐던 것이 아니라 어차피 하드웨어의 성능이 좋아 충분히 빠르다는 안일한 핑계로 문제를 외면했을 지도 모르겠습니다.

하지만 최근에 CloudFront로 캐싱 서버를 구축하면서, 아무리 짧은 시간의 지연이라도 사용자 경험을 크게 해칠 수 있다는 것을 깨달았습니다. 그래서 이 글을 작성하게 되었고, 짧은 코드 한줄이라도 얼마나 신중하게 작성해야 하는지 깨달은 것 같습니다.

최고의 사용자 경험을 위해선 클라이언트의 역할도 중요하겠지만, 컨텐츠를 빠르고 안정적으로 응답하는 서버의 역할도 매우 중요할 것입니다. 그런 의미에서 서버 개발자인 제가 기본을 소홀히 한 것 같아 반성하게된 하루였습니다.

--

--