[JS] promise.All 직접 만들어 본 사람?

준리·2022년 9월 28일
0

비동기 프로그래밍

목록 보기
7/10
post-thumbnail

class methods: Promise.all

async/await 가 나왔으므로, 이제 프로미스를 안쓴다? 비동기에 await를 남발하게 되면 도리어 처리가 느려지는 경우도 종종 발생한다. 병렬적으로 Promise를 동시에 실행시키는 멋진 Promise.All을 직접 만들어보고, 이를 사용해야하는 실제 사례를 함께 알아보자.

비동기 프로그래밍의 산을 함께 넘어보자!

Promise.all

  • 여러 Promise Fn을 동시실행(concurrency)
  • 모두 성공(fulfilled) 시 시간과 무관하게 순서 보장!
  • 하나라도 실패(rejected) 시 바로 catch로!
  • Promise.all(iterables).then().catch(...
    ⇐⇒ new Promise.all([p1, p2]).then(arr => console.log(arr)); // [res1, res2]

promise 메서드

  • promise.all()은 모든 프로미스 fulfilled 되길 기다림
    하나라도 에러 발생 시, 모든 프로미스 요청이 중단됨.
  • promise.allSettled()모든 프로미스가 settled 되길 기다림.
  • promise.race() 넘겨진 모든프로미스들 중 하나라도 settled되길 기다림
  • Promise.any() 넘겨진 프로미스 중 하나라도 fulfilled 되길 기다림

다음 코드에서 promiseAll 함수를 작성하시오.

import { assertArray } from "../utils/test-utils.js";

const randTime = (val) =>
    new Promise((resolve) => {
        const delay = Math.random() * 1000;
        console.log("randtime :>> ", val, delay);
        setTimeout(resolve, delay, val);
    });

    const promiseAll = (params) => {
        
    }

const vals = [1, 2, 3];
// const randTime = val =>
// 	new Promise(resolve => setTimeout(resolve, Math.random() * 1000, val));

promiseAll([randTime(1), randTime(2), randTime(3)])
    .then((arr) => {
        console.table(arr);
        assertArray(arr, vals);
    })
    .catch(console.error);

promiseAll([randTime(11), Promise.reject("RRR"), randTime(33)])
    .then((array) => {
        console.log("여긴 과연 호출될까?!");
    })
    .catch((error) => {
        console.log("reject!!!!!!>>", error);
    });

🎃문제정의

  1. TDD를 위해 기존에 작성한 assert를 import 해왔다.
const assertArray = (arr1, arr2) => {
    if (assertEqual(arr1, arr2)) {
        console.log(arr1, "==>", "통과");
    } else {
        console.log(arr1, arr2, "불통");
    }
};
  1. 비동기 실행 환경을 위해 randTime함수를 직접 만든 promiseAll함수의 array 인자로 활용하였다.

  2. thencatch를 활용하기 위해선 promiseAll 함수는 프로미스 객체를 return 해야한다.

Promise.All 직접 구현하기

const promiseAll = (arr) => {
    return new Promise((resolve, reject) => {
        for (const item of arr) {
            console.log(item);
        }
    });
};
  1. PromiseAll 이라는 함수를 만들고 arr를 매개 변수로 받는다.
    • arr = [randTime(1), randTime(2), randTime(3)]
  2. 프로미스 객체를 리턴하기 위해 return 에 new Promise를 해준다.
  3. arr 값은 배열에 담겨있기 때문에 for ~ of문을 사용하여 신나게 돌린다. 일단 console.log를 찍어보자

promise pending value

하나하나의 값들은 Promise pending 값으로 찍히는 것을 볼 수 있다. 이 값들을 settled 해주기 위해선 무엇을 해야할까. 내부에서 then을 돌려야 한다.

const promiseAll = (arr) => {
    return new Promise((resolve, reject) => {
        for (const item of arr) {
            item.then(console.log);👈🏽
        }
    });
};

item이 then을 만나게해서 그 값을 console.log 찍어보았다.

오호호 각자의 값이 찍히는 것을 볼 수 있다. 이것을 arr에 담아서 resolve 해주면 될까? 일단 해보자.

순서보장이 안된다?!

const promiseAll = (arr) => {
    const 리턴배열 = [];👈🏽
    return new Promise((resolve, reject) => {
        for (const item of arr) {
            item.then((res) => {
                리턴배열.push(res);👈🏽
                if (리턴배열.length === arr.length) {
                    resolve(리턴배열);
                }
            });
        }
    });
};

리턴할 배열 리턴배열 빈 객체를 하나 만들어줬다. 이로서 이 함수는 클로저가 되었다. 두둔
then 메서드로 받은 값을 리턴배열에 push 해서 때려 넣었다.
resolve 조건은 리턴배열의 길이와 매개변수로 준 arr의 길이가 같을 때 반환하게 했다.
결과 값을 한 번 확인해보자.

오 된다??? 근데? 안된다?

그렇다. 순서보장이 안되고 지맘대로 arr에 들어온 순서대로 때려박는 것이었다.

필수 : - 모두 성공(fulfilled) 시 시간과 무관하게 순서 보장!

그래서 index 값을 써보기로 했다.

const promiseAll = (arr) => {
    const 리턴배열 = [];
    return new Promise((resolve, reject) => {
        for (const item of arr) {
            const idx = arr.indexOf(item);👈🏽
            item.then((res) => {
                리턴배열[idx] = res;👈🏽
                if (리턴배열.length === arr.length) {
                    resolve(리턴배열);
                }
            });
        }
    });
};

오오오? 되는데??? 안된다?

                if (리턴배열.length === arr.length) {
                    resolve(리턴배열);
                }

empty 값도 index 값을 부여받아 리턴배열의 length로 잡혀서 그냥 resolve 되는 것이다.
이는 제동 장치가 필요해보인다.

const promiseAll = (arr) => {
    const 리턴배열 = [];
    let pending = 0; 👈🏽
    return new Promise((resolve, reject) => {
        for (const item of arr) {
            const idx = arr.indexOf(item);
            item.then((res) => {
                리턴배열[idx] = res;
                pending += 1;👈🏽
                if (pending === arr.length) {👈🏽
                    resolve(리턴배열);
                }
            });
        }
    });
};

pending이라는 변수에 cnt 기능을 붙여 동작하게했다.
for of 가 한 번 돌 때마다 +=1 해주면서 arr.length가 될 때 resolve를 return 하게 했다.

Error 처리까지 완료한 최종코드

const promiseAll = (arr) => {
    const 리턴배열 = [];
    let pending = 0;
    return new Promise((resolve, reject) => {
        for (const item of arr) {
            const idx = arr.indexOf(item);
            item.then((res) => {
                리턴배열[idx] = res;
                pending += 1;
                if (pending === arr.length) {
                    resolve(리턴배열);
                }
            }).catch(reject);👈🏽
        }
    });
};

then 뒤에 .catch(reject); 해주면서 에러는 잡아낸다.
.catch(reject); === .catch(error => reject(error)와 같은 것이다.

결론

Promise.All이 어떤 아이인지 인지 할 수 있었다. 다음은 이를 활용하는 사례에 대해 알아보고자 한다. 분량조절 실패로 다음 편에 !

출처

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

profile
트렌디 풀스택 개발자

0개의 댓글