[JS] await 대신 Promise.All을 사용해야 할 때

준리·2022년 9월 28일
3

비동기 프로그래밍

목록 보기
8/10
post-thumbnail

Promise.All을 사용해야하는 사례

비동기 활동을 모두 완료하고 진행해야할 때 우리는 Promise.All을 사용해야한다.

filter 안에 비동기

filter에서의 실수

  • 각각이 Promise(thread) 반환. 즉 Never False!!
// delay에 따라서 sec 값을 비동기적으로 반환해주는 함수
const afterTime = (sec) => {
  console.log("afterTime>>", sec);
  if (sec < 1 || sec > 3)
      return Promise.reject(new Error("Not valid sec range!!"));
  return new Promise((resolve) => setTimeout(resolve, sec * 1000, sec));
};


const odds = [1, 2, 3].filter(async val => {
  const r = await afterTime(val);
  console.log(r);
  return r % 2 === 1;
});
console.log('odds=', odds);

// [1, 2, 3]
// 원하는 결과 값 [1, 3] // 홀수 값

arr.filter 메서드의 관심은 true와 false 뿐이다.

이 정신나간녀석은 결괏값이 출력되기도 전에 filter의 return 값을 출력했다. 왜?
filter에 async를 건 순간부터 예정되있던 일이다. async는 해당 값을 프로미스로 리턴하기 때문에 배열에는 모두 truesy한 값이 담기 때문이다.
[1, 2, 3] = [프로미스, 프로미스, 프로미스] = [truesy, truesy, truesy]

여기서 프로미스all을 써보면 어떨까

const res = [1, 2, 3].map(afterTime);
const resres = await Promise.all(res);
const resresres = resres.filter((a) => a % 2 === 1);

console.log("🚀res", res);
console.log("🚀 resres", resres);
console.log("🚀 resresres", resresres);

풀어서 쓰면 다음과 같은 방식으로 해결 할 수 있다.

map을 활용해 해당 프로미스들을 arr에 담아둔다. Promise.all을 활용해 그 값들을 fulfiled 한다. 그리고 filter를 돌려 활용한다.

더 간결하게 표현하자면 아래 코드로 같은 값을 얻을 수 있다.

console.log(
    "odds=",
    (await Promise.all([1, 2, 3].map(afterTime))).filter((a) => a % 2 === 1)
);

async 없이 왜 Promise.all은 await를 사용하는가?

그 것은 지금 Promise.all의 위치가 전역 global 환경이기 때문이다.

근데 여기서 async/await 가 필요할까?

const odds = (
    await Promise.all(
        [1, 2, 3].map( async (val) => { 👈🏽
            const r = await afterTime(val); 👈🏽
            console.log(r);
            return r;
        })
    )
).filter((res) => res % 2 === 1);
console.log("odds=", odds);

정답은 필요없다!
비동기처리는 Promise.all 이 해결해주기 때문에`로 없애는게 좋다.

map과 filter를 써도 같은 값이 나오는데 map을 쓰는 이유는?

const afterTime = (sec) => {
    console.log("afterTime>>", sec);
    if (sec < 1 || sec > 3)
        return Promise.reject(new Error("Not valid sec range!!"));
    return new Promise((resolve) => setTimeout(resolve, sec * 1000, sec + 4));
};👈👈👈

const odds = (
    await Promise.all(
        [1, 2, 3].filter((val) => { 👈
            const r = afterTime(val);
            console.log(r);
            return r;
        })
    )
).filter((res) => res % 2 === 1);
console.log("odds=", odds);

차이점을 알아보기 위해 afterTime의 return 인자 sec에 + 3초 해줘보겠다.

array.filter를 활용했을 때!

매우 빠르다. 11.559ms 오오... 근데 우리가 원한 값이 아니다.
위에서 말한 truesy 한 값을 배출하기 때문에 r의 값의 비교없이 돌아간다.

array.map을 활용했을 때!

3초가 걸렸다. 결과값을 확인하고 그 결과 값을 반환하는 배열이 출력되었다.

다양한 TDD를 해봐야하고, 비동기에서는 filter를 쓰는 것을 심각하게 고려해봐야 할 것이다.

promiseAll by async function

//다음 코드를 병렬로 실행하여 3.x초에 수행되도록 promiseAll 함수를 재작성(refactoring)하시오.

const afterTime = sec => {
  console.log('afterTime>>', sec);
  if (sec < 1 || sec > 3) return Promise.reject(new Error('Not valid sec range!!'));
  return new Promise(resolve => setTimeout(resolve, sec * 1000, `${sec}`));
};


const promiseAll = async promises => {
  const results = [];
  for (let i = 0; i < promises.length; i += 1) {
    results[i] = await promises[i](i + 1);
  }
  return results;
};

console.time('async-promiseAll');
const pfns = [afterTime, afterTime, afterTime];
const rets = await promiseAll(pfns);
console.log('rets>>>', rets);
console.timeEnd('async-promiseAll');

async/await를 사용해 해당 함수가 비동기를 병렬적으로 수행하는 모습(낭비)을 보고 있다.
3초면 될 일을 6초를 소비했다. 이를 어쩌면 좋을까

for-await-of (ES2018)

for-await-of MDN
for await of 구문은 반복문 내부에서 실행되는 비동기 서비스들에 대한 순서를 보장해준다.

const promiseAll = async (promises) => {
    const results = [];

    const res = promises.map((item, idx) => item(idx + 1)); // (1)
    for await (const k of res) results.push(k); // (2)

    return results;
};
  1. 실행되지 않은 함수를 실행해서 promise로 반환된 객체를 보관해두는 곳이다.
    promises를 map으로 돌려 idx 값을 활용해 리턴된 배열을 만든다.

  2. for-await-of 구문을 활용해 병렬적으로 해당 promose들을 리턴해 results 배열에 담아준다.

  3. results 배열을 반환한다. 비동기 함수들이 병렬적으로 실행되는 것을 알 수 있다.

결론

무조건적으로 최신 문법을 사용해야한다고 생각했다. 무조건 적으로 async/await를 사용한다면 위와 같은 문제를 마주칠 것이다. 비동기에 대한 이해와 문법적 활용이 뒷받침되야 비로소 함수형프로그래밍의 비동기의 진짜 모습을 마주할 수 있을 것이다. (사실 아직도 잘 모르겠다^^) 비동기의 산을 반드시 넘는다!!

출처

SSAC 영등포 교육기관에서 풀스택 실무 프로젝트 과정을 수강하고 있다. JS전반을 깊이 있게 배우고 실무에 사용되는 프로젝트를 다룬다. 앞으로 그 과정 중의 내용을 블로그에 다루고자 한다.

profile
트렌디 풀스택 개발자

0개의 댓글