킹의 개발일지

Promise.all, Promise.allSettled 본문

프로그래밍 언어/Js

Promise.all, Promise.allSettled

k1ng 2023. 9. 11. 03:56

문제의 발단

일출, 일몰 시간을 가져오기 위해 공공데이터 포털의 한국천문연구원_출몰시각 정보 api를 사용할 일이 생겼다.

 

로직상 4일치의 일출, 일몰 시간이 필요했지만 한국천문연구원_출몰시각 정보은 한 번 요청에 하루치 데이터만 응답해주고있다. 심지어 응답값도 xml이라구.. ㅠ

 

때문에 4일치 데이터를 얻기 위해 4개의 요청을 날려야 하는 상황이 생겼다. 간단하게 4번의 요청을 하면 되지 않을까 생각이든다.

const data1 = await fetch(url_1);	// 오늘
const data2 = await fetch(url_2);	// 내일
const data3 = await fetch(url_3);	// 모레
const data4 = await fetch(url_4);	// 글피
...

 

그러나 이 요청의 결과는 waterfall 현상으로 이어질 것이다. 즉 병목현상, 하나의 요청을 처리하는 동안 다른 요청들은 손가락 빨며 기다리는 일이 발생할 것이다. 만약 하나의 요청에 1초가 걸린다면 총 10번의 요청은 10초가 걸린다는 이야기다.

 

그럼 어떻게 해야 waterfall 현상을 방지할 수 있을까?


Sequential Data Fetching, Parallel Data Fetching

Sequential Data Fetching

우선 해결방법을 제시하기 전에, Sequential Data Fetching에 대해서 알아보자.

Sequential Data Fetching은 의도적으로 waterfall을 일으킨다. 즉, 하나의 요청을 처리하고 나서 다음 요청을 처리하는 것이다. 이는 안 좋게 느껴질 수 있지만, 꼭 나쁜것이 아니다. 내 프로젝트에서도 이 패턴을 사용했는데, 특정지역의 자외선 지수를 가져오기 위해서는 행정코드를 필요로 한다. (공공 데이터 포털에서 제공하는 api는 요청 쿼리가 통일 돼있지 않아서 매우 불편하다.. 단기예보는 위도, 경도를 필요로 하고, 중기예보는 xml파일에서 제공하는 지역 코드를 사용해야한다...)

 

Geolocation api를 사용하면 위도와 경도를 얻을 수 있지만 행정코드는 얻을 수 없다. 때문에 Kakao Local api를 이용해서 위도, 경도를 행정코드로 변환해야 했다.

 

때문에, 자외선 지수는 무조건 Kakao Local api의 응답이 있고 나서야만 요청을 날릴수 있다. 이때 Sequential Data Fetching을 사용할 수 있다.

Parallel Data Fetching

다시, 위에서 제시한 waterfall을 해결할 방법으로 Parallel Data Fetching 방법이 있다. 자바스크립트에서는 Promise.all(), Promise.allSettled()를 통해서 문제를 해결할 수 있다.

Promise.all

mdn에서 Promise.all을 다음과 같이 설명한다.

'이 메서드는 여러 프로미스의 결과를 집계할 때 유용하게 사용할 수 있습니다. 일반적으로 다음 코드를 계속 실행하기 전에 서로 연관된 비동기 작업 여러 개가 모두 이행되어야 하는 경우에 사용됩니다.'

 

여기서 프로미스는 fetch 함수가 반환하는 값으로 이해하면된다.(fetch는 Promise를 반환한다구! 때문에 await로 resolve해줄 필요가 있다) 그리고 '연관된 비동기 작업 여러개가 모두 이행되어야 하는 경우' 이것이 내가 필요한 부분이다.

 

때문에 위 예시를 Promise.all을 통해서 처리하면 다음과 같다. Promise.all도 Promise를 반환하기에 리졸빙된 값을 얻기 위해서는 await로 기다려 줄 필요가 있다.

const reponse1 = fetch(url_1);
const reponse2 = fetch(url_2);	
const reponse3 = fetch(url_3);	
const reponse4 = fetch(url_4);	
// response에는 Promise가 담긴다.

const result = await Promise.all([response1, response2, response3, response4]);
// Promise.all도 Promise를 반환 때문에 리졸빙된 데이터를 가져올려면 await가 필요하다.

 

그럼 이런 생각이 들 수도 있다.

'요청 중 하나가 잘못되면 어떻게 될까?' 요청 모두가 정상적인 응답만 줄 것이 아니기에 이 또한 생각해야한다.

 

mdn은 Promise.all의 거부에 대해서 다음과 같이 설명한다.

거부
주어진 프로미스 중 하나라도 거부하면, 다른 프로미스의 이행 여부에 상관없이 첫 번째 거부 이유를 사용해 거부합니다.

즉, 하나라도 reject 된다면 reject 됐던 이유로, 모두가 reject 된다는 것이다. 그래서 Promise.all은 처리해야할 데이터가 서로 연관된 Primise일  경우에 유용하다.

 

그렇다면 하나가 거부되더라도, 나머지 성공한 데이터들을 전달받고 싶을때는 어떻게 해야할까 이때는 Promise.allSettled를 사용하면 된다.

 

Promise.allSettled

mdn에서 Promise.allSettled 메서드를 다음과 같이 설명한다.

일반적으로 서로의 성공 여부에 관련 없는 여러 비동기 작업을 수행해야 하거나, 항상 각 프로미스의 실행 결과를 알고 싶을 때 사용합니다.
그에 비해, Promise.all()이 반환한 프로미스는 서로 연관된 작업을 수행하거나, 하나라도 거부 당했을 때 즉시 거부하고 싶을 때 적합합니다

 

하나라도 거부된다면 모두 거부되는 Promise.all에 비해 Promise.allSetteld는 거부된 Promise만 처리하고 나머지는 결과를 반환한다.


이렇게 여러 요청에 대해서 waterfall 현상을 방지할 수 있는 Pararell Fetching 방법에 대해서 알아보았다. 

 

Next.js 13으로 마이그레이션 하면서 Docs를 훑어보고 있었는데, Docs에서도 두 패턴에 대해서 설명하고 있었다.

Data Fetching에 대해서 공부도 하며, 프로젝트에도 적용시켜 성능 개선을 할 수 있어서 Docs는 역시 읽어봐야 겠구나 싶었다.

'프로그래밍 언어 > Js' 카테고리의 다른 글

Optional Chaining  (0) 2023.04.27
JavaScript - async & await  (0) 2023.03.30
JavaScript - Promise (then, catch)  (0) 2023.03.29