Promise.all() 로 비동기 처리를 구현해 보자

Jung Ian·2022년 12월 15일
3
❗ Node.js (v14 이상) 를 대상으로 작성한 코드입니다.

요약

  • Promise.all() 은 여러 개의 Promise 들을 비동기적으로 실행하여 처리할 수 있다.
  • Promise.all() 은 여러 개의 Promise 들 중 하나라도 reject 를 반환하거나 에러가 날 경우, 모든 Promise 들을 reject 시킨다.

시작하기 전에

타입스크립트(자바스크립트)를 사용하면서 비동기 처리에 대해 알게 되었다. 콜백지옥, Promise, async/await 등 비동기 처리에 관한 내용을 공부하게 되면서 비동기 처리의 장점에 대해 알 수 있었고 이번 프로젝트에서 드디어 사용해 볼 기회가 생겼다.

Promise.all()

이번 프로젝트에서 비동기 처리를 위해 Promise.all() 메소드를 사용했고, 이번 포스트를 통해 이 메소드의 사용 방법에 대해 정리하고자 한다.

비동기 처리

Promise.all() 은 여러 개의 프로미스(Promise) 를 비동기적으로 실행한다. 따라서 여러 개의 Promise 처리를 비동기적으로 실행하고자 할 때, 사용할 수 있다.

  • 1-1. sample code

    async function sampleFunc(): Promise<void> {
        // time start
        console.time('promise all example');
    
        // first promise
        const fetchNameList = async (): Promise<string[]> => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                  const result: any = ['Jack', 'Joe', 'Beck'];
                  resolve(result);
                }, 300);
            });
        };
    
        // second promise
        const fetchFruits = async (): Promise<string[]> => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                  const result: any = ['Apple', 'Orange', 'Banana'];
                  resolve(result);
                }, 200);
            });
        };
    
        // third promise
        const fetchTechCompanies = async (): Promise<string[]> => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                  const result: any = ['Apple', 'Google', 'Amazon'];
                  resolve(result);
                }, 400);
            });
        };
    
        // promise all
        const result: any[] = await Promise.all([
            fetchNameList(),
            fetchFruits(),
            fetchTechCompanies(),
        ]);
    
        // time end
        console.timeEnd('promise all example');
    
        console.log('%j', result);
    }
    
    // execute
    sampleFunc();
  • 1-1. 출력 결과

    • 전체 실행 시간이 402ms 이다. 각 Promise 들의 처리 시간(300ms, 200ms, 400ms) 중 가장 긴 시간인 400ms 와 비슷한 시간이다. 이를 통해 비동기적으로 실행되었음을 알 수 있다.

      promise all example: 402 ms
      
      [["Jack","Joe","Beck"],["Apple","Orange","Banana"],["Apple","Google","Amazon"]]
  • 추가

    • 아래와 같이 각각의 처리 결과를 변수에 담을 수도 있다.

      const [names, fruits, companies] = await Promise.all([
        fetchNameList(),
        fetchFruits(),
        fetchTechCompanies(),
      ]);
      
      console.log('%j, %j, %j', names, fruits, companies);

동기 처리

만약 이 코드를 동기적으로 실행한다면 어떻게 될까?

  • 1-2. sample code
    // time start
    console.time('promise all example');
    
    const names: string[] = await fetchNameList();
    const fruits: string[] = await fetchFruits();
    const companies: string[] = await fetchTechCompanies();
    
    // time end
    console.timeEnd('promise all example');
    
    console.log('%j, %j, %j', names, fruits, companies);
  • 1-2. 출력 결과
    • 905ms. 각 Promise 들의 처리 시간(300ms, 200ms, 400ms)을 모두 더한 값인 900ms 와 비슷하다. 각각의 처리가 서로 영향을 주지 않는다면 비동기적으로 실행하는 것이 훨씬 더 빠른 속도로 처리할 수 있다.

      promise all example: 905 ms
      
      ["Jack","Joe","Beck"], ["Apple","Orange","Banana"], ["Apple","Google","Amazon"]

에러 처리

Promise.all() 메소드를 사용할 때 주의해야 할 점이 있다. 파라미터로 여러 개의 Promise 들이 배열 형태로 들어가게 되는데, 이 중 하나라도 reject 가 되거나 에러가 발생할 경우, 모든 Promise 들이 reject 된다. 아래의 예제 코드를 통해 확인해 보자.

  • 1-3. sample code
    • 3개의 Promise 중 하나를 reject 로 반환한다.

      ...
      
      // third promise
      const fetchTechCompanies = async (): Promise<string[]> => {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            const result: any = ['Apple', 'Google', 'Amazon'];
            reject(result);
          }, 400);
        });
      };
      
      ...
  • 1-3. 출력 결과
    • reject 로 수정한 fetchTechCompanies() 의 결과가 에러로서 출력되었다.

    • 어느 Promise 를 수정하더라고 결과는 동일하다. Promise.all() 의 여러 개의 Promise 들 중 1개라도 reject 를 반환하거나 에러가 발생한다면 Promise.all() 은 모든 결과를 reject 로 반환한다.

      Error: thrown: Array [
        "Apple",
        "Google",
        "Amazon",
      ]

따라서 Promise.all() 을 사용할 때에는 에러 처리를 어떻게 할 것인가를 정하는 것이 중요하다. 이번 프로젝트에서는 혹시라도 처리에 실패할 경우 빈 배열을 반환하도록 했기에 아래의 코드처럼 구현했다.

  • 1-4. sample code
    ...
    
    // result 
    let result: any[] = [];
    
    // parameter array
    const promiseList: any[] = [
      fetchNameList(),
      fetchFruits(),
      fetchTechCompanies(),
    ];
    
    // try ~ catch
    try {
      result = await Promise.all(promiseList);
    } catch (e) {
    	// default value: empty array
      result = Array(promiseList.length).fill([]);
      console.error('promise-all', e);
    }
    
    ...
  • 1-4. 출력 결과
    promise-all [ 'Apple', 'Google', 'Amazon' ]
    
    promise all example: 439 ms
    
    [[],[],[]]

기본값(Default value) 없이 하나의 처리라도 reject 될 경우, 에러를 던지는 경우도 있기 때문에 구현해야할 사양에 잘 맞춰 에러를 처리해야 한다.

마무리

Promise.all() 메소드는 여러 개의 Promise 들이 비동기적으로, 정상적으로 끝났음을 알 수 있는 좋은 방법이다.

Promise.all() 메소드가 에러를 던지지 않고 정상적으로 종료되었다는 것은 모든 Promise 들을 정상적으로 처리했다는 확실한 증거이다. (단 하나의 Promise 라도 reject 되거나 에러를 발생할 경우, 정상적으로 종료되지 않기 때문에)

에러 처리에 신경써서 사용한다면, 성능을 개선할 수 있는 좋은 선택지가 될 수 있다.


profile
Tokyo Dev

0개의 댓글