성장, 그리고 노력

부족하더라도 어제보다 더 잘해지자. 노력은 절대 배신하지 않는다.

Javascript

언제 Promise.all을 사용해야 될까?

제이콥(JACOB) 2020. 8. 22. 02:00
async await는 Promise를 가독성 좋게 사용하는 한 가지 방법에 불과합니다. 

이미 앞에 글에서 봤듯이 자바스크립트를 통해 비동기 코드를 처리하는 가장 기본적인 방법은 콜백 함수, 프라미스(Promise), Async-await 등이 있었다. 그리고 각각은 장점과 단점이 있었고, 각각의 함수가 중첩되는 상황이 깊어질수록 콜백 함수는 콜백헬을 만들고, 여러 프라미스가 체인닝되어 선언되었다면 에러를 디버깅하는데 힘들었다. 그에 반해 async await를 간결한 코드와 기존 에러 핸들링 방식이었던 try~catch문을 통해 더 깔끔한 처리(?)가 가능했다. 

 이런 이야기만 들으면 async-await만 쓸 것같지만, 실제로는 꼭 그렇지 않다. 여기에 대한 이야기를 차근차근 풀어보자.


여러 개의 비동기 처리를 병렬로 하고 싶다면?

 여기서 말하는 여러 개의 비동기 처리란, 순서가 보장되지 않아도 되는 상황을 말한다. 예를 들어 "유저를 조회하고, 거기서 얻은 유저 아이디를 통해 찜하기 상품을 가져온다."와 같은 경우는 아니다. 순서와 무관한, 예를 들면 "상품 목록과 상품 목록의 총개수를 조회한다"의 경우이다. 

 그렇다면 이런 순서와 무관한 비동기 작업을 병렬로 진행하려면 어떻게 해야 할까? 일단 async await를 이용하여 간단한 예제 코드로 확인해 보겠다.

  async function display(text, time) {
    log(text); // <-- 이건 그냥 확인용 util 함수 입니다.
    return new Promise((resolved, rejected) => {
      setTimeout(
        () =>
          typeof text === "string"
            ? resolved("오호 스트링?")
            : rejected("누구냐 넌..."),
        time
      );
    });

 display라는 함수를 정의하였고, text와 time을 인자로 받고 있다. 이 함수는 Promise를 리턴하고 있으며, 좀 더 눈에 띄는 차이를 확이하기 위해 setTimeout을 통해 일정 시간 후에 실행되도록 하였다. 그리고 이제 이것을 실행해 보자. 

  console.time("소요시간");
    await display("jacob", 3000);
    await display("제이콥", 2000);
    await display("콥짱", 1000);
  console.timeEnd("소요시간");
  
  // 소요시간: 6000ms 약간의 오차는 날 수 있다.

 

 각각의 함수가 실행된 후 다음 비동기 함수가 실행되므로 예상했듯이 6초가 걸렸다. 하지만 이런 경우 Promise.all을 사용한다면 훨씬 단축된 시간을 확인할 수 있다.

  console.time("소요시간");
  await Promise.all([
    display("jacob", 3000),
    display("제이콥", 2000),
    display("콥짱", 1000)
  ]);
  console.timeEnd("소요시간");
  
  // 소요시간: 3005ms

  소요 시간
async await 6초
Promise.all 3초

 왜 이런 결과가 나왔을까? async-await의 함수을 하나하나 다 기다렸다가 하였고, Promise.all을 병렬로 비동기 함수를 실행시켰기 때문이다. 이것을 그림으로 보면 더 빠른 이해가 가능하다.

 위 Promise.all([...]) 코드에서 display(3000), display(2000), display(1000)가 차례대로 앞의 함수의 실행이 완료되는 것을 기다리지 않고 비동기적으로 병렬로 실행된다. 여기서 눈여겨볼 점은 "순서대로 실행되지만, 앞의 함수가 완료되는 것을 기다리지 않는다"는 것이다. 

 그리고 마지막으로 완료되는 함수까지 기달려다가 값을 반환한다. 이때 반환 순서는 실행 순서와 동일하게 준다(완료 순서가 아니다!). 그래서 각각의 완료 값을 만약 변수에 할당한다고 하면 아래와 같이 적을 수 있다. 

const [res1, res2, res3] = await Promise.all([display(3000), display(2000), display(1000)])

Promise.all의 장점 - Fail Fast

 비동기처리를 병렬적으로 해서 더 빠른 게 처리할 수 있다는 장점이 있다. 하지만 이때만 장점이 있는 게 아니라, 비동기 처리가 실패했을 경우에도 있다. Promise.all()은 중간에 어떤 함수가 에러가 났을 때 그 실패를 즉시 반환한다. 위의 코드를 예로 들어보자.

 같은 비동기 처리를 하는 경우지만, 소요 시간이 전혀 다른 것을 알 수있다.  await로 각각을 실행시켰을 경우 첫 번째 함수가 걸리는 시간인 3초를 다 채우고 에러를 반환한다. 

 반면 Promise.all()함수를 사용한 경우 비동기 함수가 병렬로 실행되기 때문에 실행 순서와는 상관없이 가장 빨리 에러를 반환하는 함수의 시간을 기준으로 동작이 종료된다. 그래서 1초밖에 걸리지 않게 된다. 

 물론 각각의 함수를 await 하는 경우에도 1초가 걸리는 display(3, 1000)함수를 맨 위로 올리면 1초만 걸리고 에러를 반환하게 된다. 하지만 현실 세계에서 가장 에러를 빨리 뱉을 거 같은 함수를 찾아서 맨 위에 적는 건 사실상 어렵다. 그리고 Promise.all()이라는 대안이 있는데, 굳이 그럴 이유도 없는 것이다

 

 

 

When to use Promise.all? - CodeSandbox

When to use Promise.all? by JungKyuHyun

codesandbox.io

 

 

반응형

'Javascript' 카테고리의 다른 글

이젠 Modern JavaScript로 publish 하자  (0) 2021.05.22
리팩터링(Refactoring)을 하는 이유  (0) 2020.09.05
CSS-in-JS  (0) 2020.08.19
webpack 뭐하는 걸까?  (0) 2020.08.18
이벤트, 그리고 버블링과 캡처링  (0) 2020.08.13