관리 메뉴

즐겁게, 코드

Promise.all 로 비동기 로직 개선하기 본문

💬 언어/Javascript

Promise.all 로 비동기 로직 개선하기

Chamming2 2021. 11. 4. 00:35

아주 오랜 옛날부터, 전설로 내려오는 코드가 있습니다.

(3학년 1학기에 수행했던 산학프로젝트 코드입니다.)

지금 보니 끔-찍 하군요...

이 코드는 간단한 그룹 매칭 서비스에서 그룹을 생성하는 역할인데요, 여러 비동기 로직이 async-await 을 통해 순차적으로 실행되는 무시무시한 모습입니다.

 

얼핏 보면 비동기 로직 제어에 성공한 것처럼 보이지만 순차적으로 실행되는 약 7회의 POST 요청으로 인해 당시 그룹을 생성하는 데에만 약 20~30초 가량의 시간이 소요되었고, UX 개선을 위해 로딩 애니메이션을 별도로 제작하는 등의 수고를 더해야만 했습니다.

 

콜백, 프라미스, async-await 문법을 거쳐오며 비동기 로직 제어는 점점 쉬워졌지만 진화의 최종형인 async-await 을 사용하는 것만이 언제나 답이 되지는 않는데요, 오늘은 위 문제를 해결할 수 있는 Promise.all 문법에 대해 소개해보도록 하겠습니다.

async-await, 과연 무적인가?

먼저 기존의 async-await은 그 이름이 시사하듯 비동기 동작(프라미스)의 상태가 완료될 때까지 기다린 후, 다음 코드를 순차적으로 읽어 나가며 실행합니다.

한번 실행하는데 각각 1초 / 2초가 걸리는 쿼리를 수행하는 예제를 생성해보도록 하겠습니다.

const userList = [
  { name: 'ethan', id: 1 },
  { name: "david", id: 2 },
  { name: 'john', id: 3 }
];

// 1초가 걸리는 쿼리
const getUserById = async (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const [user] = userList.filter(user => user.id === id)
      resolve(user)
    }, 1000)
  })
}

// 2초가 걸리는 쿼리
const getAllUsers = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(userList)
    }, 2000)
  })
}

const fetchData = async () => {
  console.time('소요시간 : ')
  const user = await getUserById(2)
  const userList = await getAllUsers()
  // 불러온 데이터를 출력하기까지 최소 3초가 소요된다.
  console.log(user)
  console.log(userList)
  console.timeEnd('소요시간 : ')
}

fetchData()

async-await은 태스크의 순서가 보장되어야 할 때는 분명 강력하지만, 각각의 태스크가 서로 연관성이 없는 작업일 때는 앞의 작업이 끝날 때까지 기다릴 필요가 없습니다.

 

자, 뭔가 다른 방법은 없을까요?

Promise.all

바로 이런 상황에서 Promise.all이 빛을 발할 수 있습니다.

Promise.all은 여러 비동기 동작(프라미스)을 하나로 묶어 하나의 프라미스처럼 관리할 수 있게 해주는데요, 위의 코드를 Promise.all을 사용해 개선해 보겠습니다.

const userList = [
  { name: 'ethan', id: 1 },
  { name: "david", id: 2 },
  { name: 'john', id: 3 }
];

const getUserById = async (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const [user] = userList.filter(user => user.id === id)
      resolve(user)
    }, 1000)
  })
}

const getAllUsers = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(userList)
    }, 2000)
  })
}

const fetchData = async () => {
console.time('소요시간 :')
  // Promise.all() 은 실행할 비동기 태스크들이 담긴 배열을 인자로 받습니다.
  const [user, userList] = await Promise.all([getUserById(2), getAllUsers()])
  console.log(user)
  console.log(userList)
  console.timeEnd('소요시간 :')
}

fetchData()

Promise.all은 여러 비동기 태스크를 동시에(병렬적으로) 실행하고, 가장 마지막 태스크가 완료될 때 완료 상태의 프라미스를 반환합니다.

따라서 위의 예제의 실행시간은 모든 태스크를 더한 1초 + 2초가 아닌, 가장 오래 걸린 태스크의 실행시간인 2초 남짓한 시간만이 소요된 모습입니다.

주의사항

Promise.all은 잘 사용하면 비동기 로직의 완료시간을 크게 줄여줄 수 있는 문법이지만, 반드시 짚고 넘어가야 할 점이 하나 있습니다.

바로 Promise.all 내부에서는 태스크들의 순서가 제어되지 않기 때문에, 태스크의 순서가 중요한 경우라면 절대로 Promise.all을 통해 관리해서는 안된다는 점입니다.

TL;DR;

  • 실행할 비동기 태스크들의 순서가 중요하지 않다면, Promise.all 로 완료시간을 크게 앞당길 수 있다.
    (주로 여러 데이터를 페칭하는 경우)
const fetchInitialData = async () => {
    // 여러 데이터를 페칭할 경우, 병렬적으로 비동기 로직을 수행할 수 있다!
    const [userData, companyData, scoreData] = await Promise.all([
        getUserList(),
        getCompanyList(),
        getScoreList()
    ])
    console.log(userData, companyData, scoreData);
}
  • 단, 각 태스크의 실행 순서가 중요한 경우라면 기존의 async-await 을 사용하자!
    (주로 여러 데이터의 페칭뮤테이션을 함께 수행하는 경우)
const setInitialData = async () => {
    // update, get의 순서가 보장되어야 하는 경우
    const userData = await getUserList();
    await updateUserList({name: "chanmin", age: 25});
}
반응형
Comments
소소한 팁 : 광고를 눌러주시면, 제가 뮤지컬을 마음껏 보러다닐 수 있어요!
와!! 바로 눌러야겠네요! 😆