본문으로 건너뛰기
뒤로 가기

[JS] 🚀Promise와 Async/Await 완전 이해: 병렬로 비동기 데이터 한 번에 받아오기

article_thumbnail

썸네일 메이커(링크) 잘 만들었으니 다들 가서 확인해보세요!

0. 암튼 기다려줘!

최근에 그런 질문을 봤었다.

setTimeout을 이용해 비동기처럼 환경을 구성해서 데이터를 받아와 처리하는 코드인데 이게 왜 안 돌아가냐고.

코드는 가져올 수 없고, 대충 원하는 결과는 이러했다.

1
2
3
Done!

위처럼 나오게 하고 싶은데

Done!
1
2
3

결과는 이랬다는 것이다.

비동기를 모르는 개발자나, Promise에 대해 깊이 생각해보지 않은 개발자였다면 어라 이거 왜 이러지?? 라고 생각했을 것이다.

그러나 나는 코드를 보자마자 문제점을 떠올렸다.

이거 Promise 가 없네

그랬다. Promise 없이 그저 setTimeoutasync/await를 혼합해 놓고선 setTimeoutawait를 통해 기다리라고 했던 것이다.

자바스크립트 최신 문법만을 배우는 뉴비들에게서 자주 나타나는 문제이다. async/await를 맹신한 나머지 Promise를 구시대의 유물로 취급하여 쓰지 않으려는 문제.

물론 최신 문법이 편한 건 맞지만, async/await는 길어지는 콜백 지옥, 메서드 체이닝을 피하기 위한 방법이지, Promise를 저 밑바닥으로 보내버리는 방법이 절대 아니다!

이제부터 Promiseasync/await 를 사용해 비동기 최적화를 해보자!

1. Promise는 아직 필요하고, await는 만능이 아니다.

Promise에는 여러 메서드들이 있다. 여러 비동기 중 가장 빨리 가져오는 것만 반환한다든지, 여러 개의 비동기를 병렬로 한 번에 가져온다든지 하는 등등…

그리고 우리는 후자를 살펴볼 것이다.

Promise

MDN Promise 문서

사실 문서를 읽는 게 정말 정말 좋은 선택이지만, TL;DR를 위해 간단히 요약하자면,

return new Promise((resolve, reject) => {
  //성공하면
  resolve('반환 값')

  //실패하면
  reject('반환 값')
})

이렇게 Promise 구조를 익혀두면 된다. 주로 setTimeout을 안에서 사용해 시간이 지나면 resolve하게 하는 식으로 테스트를 하곤 한다.

async / await

역시나 TL;DR를 위해서 간단히 하고 싶은 말만 하자면,

await는 뭐든 연산이 끝날 때까지 기다려주는 만능 코드가 아니다!

await는 Promise의 pending 상태가 끝날 때까지 기다려주는 것이지, setTimeout의 일정 시간 이후에 실행 컨텍스트에 추가되고 실행되는 것을 기다려주는 것이 아니다.

이 부분은 따로 실행 컨텍스트를 공부하면 이해하게 될 것이다.

2. 병렬로 비동기 데이터를 한 번에 받아온다고?

원래 이해하기 위해서는 단계가 필요하다.

  1. setTimeout으로 비동기처럼 꾸미기
  2. 배열을 비동기로 가져오기
  3. 배열의 아이템을 각각 순서대로 비동기로 가져와보기
  4. 어라 안 되네?
  5. await를 사용해서 다시 각각 순서대로 비동기로 가져와보기
  6. 어라 배열 메서드들이 안 먹히네?
  7. 기본 for 문을 사용하면 되는구나!
  8. 순차적으로 불러오는 건 가능한데, 이러면 시간이 오래 걸려
  9. 다른 방법이 없을까?

이러한 중간 과정 다 생략하고, 우리가 원하는 결과를 빠르게 보자.

배열의 아이템을 Promise.all()을 사용해 병렬로 한 번에 가져오기

백문이 불여일견이라고, 코드를 보면 이해가 쉽다.

const getPromiseCrewList = () => {
  // 크루 리스트를 비동기로 반환하는 함수
  return new Promise(resolve => {
    // Promise를 반환해줘야 await 사용가능
    setTimeout(() => {
      const crewList = ['Spike', 'Ain', 'Jet', 'Faye', 'Edward']
      resolve(crewList) // crewList를 반환해준다.
    }, 2000)
  })
}

const processParallelPromise = name => {
  // 각각의 이름들에 대해 비동기로 동작하는 함수
  return new Promise(resolve => {
    setTimeout(() => {
      name.length > 3 ? resolve(true) : resolve(false)
    }, 1000)
  })
}

const startParallelPromise = async () => {
  // 비동기로 가져온 배열의 아이템을 각각 비동기 함수에 넘겨주는 함수
  console.time('Parallel')
  const crew = await getPromiseCrewList()
  // ["Spike", "Ain", "Jet", "Faye", "Edward"], 3초 뒤에 배열을 반환한다.
  const mappingPromiseArray = crew.map(c => processParallelPromise(c))
  // [Promise, Promise, Promise, Promise, Promise], 이처럼 Promise의 배열로 만들어준다.
  const makeParallelPromiseArray = Promise.all(mappingPromiseArray)
  // Promise {<pending>}, Promise의 배열이기에 Promise.all에게 넘겨주는 게 가능해진다.
  const isOverThree = await makeParallelPromiseArray
  // Promise.all() 덕분에 병렬로 불러오게 된다.

  console.log(isOverThree) // [true, false, false, true, true]
  console.timeEnd('Parallel') // about 3002ms
}

startParallelPromise()

만약 병렬로 안 불러왔다면, 초기 2000ms + 각각 1000ms * 5가 되어 7초가 걸렸을 것이다. 하지만 병렬로 불러온 덕분에 초기 2000ms + 1000ms가 되어 겨우 3초만에 모든 데이터를 가져오는 게 가능!

코드 샌드박스로 직접 보기

콘솔창을 보면 3초만에 모든 값을 가져온 것을 알 수 있다.

3. 같이 보기

위 코드를 보면서 이해하면 다행이지만, 그게 아니라면 아래의 참고 문서 읽어보길 권한다.

비동기는 어느 자바스크립트 개발자에게나 어려운 과제이다. 낙담하지 말고 이론을 잘 살펴보면 길이 보일 것이다!


공유하기
복사됨!

이전 글
🔍[2021 Dev-Matching: 웹 프론트엔드 개발자] 회고
다음 글
📖[개발 서적] 클린 코드를 읽고