
await을 남용하면 발생하는 await 병목현상에 대해서 알아보는 포스팅이다
await병목현상은 여러 await이 연쇄적으로 발생하면 각각의 작업이 완료될 때까지 기다려야 하는 현상을 말한다.
async function example() {
const result1 = await asyncOperation1();
const result2 = await asyncOperation2();
const result3 = await asyncOperation3();
}
위와같이 여러개의 await키워드를 체이닝으로 구성해놓으면 각각의 작업이 완료될때까지 기다려야 하기 때문에 전체 작업이 느려질 수 있다.
물론 뒤에 있는 작업이 앞선 작업에 의존적인 작업이라면 해당 작업을 기다리는게 맞지만 뒤에 있는 작업이 앞선 작업과는 별개의 작업, 즉 의존적인 작업이 아니라면 위 로직의 결과물은 await 병목현상 문제점에 맞닥뜨리게 된다.
병렬적으로 처리할 수 있는 작업을 즉, 서로 관련없는 작업을 동기적으로 처리하게 되면 3초 걸릴일이 9초나 걸리는 문제가 발생하기 때문에 이를 위해서 무작정 await을 쓰는게 아닌 각 await이 서로 연관이 있나, 없나를 판단하고 해당 작업을 병렬적으로 처리해야 하는지를 결정해야 한다.
const asyncOperation1 = () =>
new Promise((resolve) => setTimeout(resolve, 1000));
const asyncOperation2 = () =>
new Promise((resolve) => setTimeout(resolve, 1000));
const asyncOperation3 = () =>
new Promise((resolve) => setTimeout(resolve, 1000));
async function example() {
console.time();
const result1 = await asyncOperation1();
const result2 = await asyncOperation2();
const result3 = await asyncOperation3();
console.timeEnd();
}
example();

위 3개의 await로직은 서로 관련이 없는 로직이라고 가정해보자
서로 관련이 없는 로직이라는건 서로 의존적이지 않은 로직이므로 동기적으로 실행되어야 하는게 아닌 비동기적으로 실행되어도 괜찮다는 의미이다.
그렇다면 우리가 기대하는 해당 로직의 시간 소요값은 1초가 될 것이다.
다만 결과는 그렇지 않다.
"example"함수의 작업을 다 해결하기 위해서는 총 3초의 시간이 걸리는 걸 확인할 수 있따.
await키워드는 비동기로직을 강제적 동기로 처리해버려서 서로 의존적이지 않은 3개의 await을 순차적으로 실행하고 있기 때문이다.
그럼 해당 문제를 어떻게 해결해야 할까?
서로 의존적이지 않은 await처리는 다른 방법을 사용해야 한다.
Promise.all
병목현상을 해결할 수 있는 Promise의 정적메서드 all이다.
all메서드는 동시처리할 때 사용하는 메서드이다.
const asyncOperation1 = () =>
new Promise((resolve) => setTimeout(() => resolve("Result 1"), 1000));
const asyncOperation2 = () =>
new Promise((resolve) => setTimeout(() => resolve("Result 2"), 1000));
const asyncOperation3 = () =>
new Promise((resolve) => setTimeout(() => resolve("Result 3"), 1000));
async function example() {
console.time();
const [result1, result2, result3] = await Promise.all([
asyncOperation1(),
asyncOperation2(),
asyncOperation3(),
]);
console.timeEnd();
console.log(result1, result2, result3);
}
example();

위와같이 Promise.all메서드를 이용해서 모든 Promise요청을 동시에 처리하게 만들 수 있다.
동시에 모든 요청을 처리하다보니 병렬적으로 처리를 하였고 총 1초의 시간이 걸렸다.
Promise.all은 배열에 들어있는 원소의 각 Promise비동기 함수들이 모두 resolve되어야 해당 Promise의 결과를 리턴 받는다.
여기서 살펴봐야 할건 배열에 들어있는 각 원소는 제각각 비동기로 실행되어 시간을 단축시킬 수 있다는 것이다.
이 밖에도 await을 사용한 로직을 논블록킹 미리 실행시키는 방법도 있다.
위 방법은 논블록킹에 대해서 더 공부하고 추후 포스팅 할 예정이다.
지금까지 프로젝트를 하면서 await문법을 생각없이 사용하고 있었는데 병목현상에 대해서 공부하고 내가 짠 코드를 다시 살펴보니 최적화가 엉망진창 이였다는걸 확연하게 느꼈다
비록 소수점의 시간차이거나 크다면 몇 초 차이가 날 수 있겠지만 사용자의 경험에서 바라본다면 해당 차이도 엄청난 차이이고 최대한 줄이면 줄일수록 더 좋은 경험을 제공할 수 있다는걸 배웠다.