본문 바로가기

JavaScript

자바스크립트 reduce 안에서 async/await 쓰기

프로젝트를 진행하며 배열에 대한 loop을 돌리면서 async/await문을 써야 할 상황이 있었는데, 일반 기본적인 함수를 async 선언해서 코딩을 해봤어도 reduce문 안에서 async/await을 쓰려고 하니 영 어색했다. 그래서 찾아보니 아주 정리가 잘 된 블로그 글이 있었다.

reduce 이외도 자바스크립트 loop안에서 async/await을 쓰는 경우가 아주 잘 정리되어있어 이해하기가 쉬웠다.

굉장히 좋은 글이라 한글로 번역해보고 싶어 이메일로 문의해봤는데 답장은 아직 안왔다. 번역해도 괜찮다고 답장이 오면 제대로 번역한 글을 올리도록 해야겠다.

혹시 원래 포스트가 궁금한 사람은 아래 링크로 가서 읽어보면 된다.

 

 

Zell Liew

Zell is a designer, developer and writer. He shares things he knows about web development on this blog.

zellwk.com

 

답장이 오기 전에, reduce문 안에서 await을 쓰는 것에 대해 간단하게 정리해보고자 한다.

//{과일: 숫자}
const fruitBasket = {
    apple: 27, 
    grape: 0,
    pear: 14 
  }

//과일의 num구하기
const getNumFruit = fruit => {
    return fruitBasket[fruit]
}

//과일 배열
const fruitsToGet = ['apple', 'grape', 'pear'];

//과일의 배열에 따라 합 구하기
const reduceLoop = async _ => {
  console.log('Start')

  const sum = await fruitsToGet.reduce(async (sum, fruit) => {
    const numFruit = await getNumFruit(fruit)
    return sum + numFruit
  }, 0)

  console.log(sum)
  console.log('End')
}

만약에 이런 코드가 있다고 하면 reduceLoop을 실행시켰을 때 결과는 아래와 같다.

  'Start'
  '[object Promise]14'
  'End'

이유는 첫번째 loop에서는 0 + 27로 제대로 계산이 되지만, 두 번째 loop에서는 Promise + 0이 된다. 알다시피 async/await문은 Promise객체를 반환하기 때문에 sum은 Promise 객체이다. 자바스크립트는 [object Promise]를 string으로 바꾸게 되고, 결국 '[object Promise]' + 0의 결과로 [object Promise]0 이 return 된다.

 

그리고 세 번째 loop에서는 두 번째 loop에서 return된 Promise객체가 accumulator인 sum이 되고, [object Promise]에 14를 더해 [object Promise]14가 콘솔 창에 출력된다.

 

따라서, 원하는 값을 출력받기 위해서는 Promise객체로 반환된 accumulator를 await 해주어야 한다.

const reduceLoop = async _ => {
  console.log('Start')

  const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {
    const sum = await promisedSum
    const numFruit = await getNumFruit(fruit)
    return sum + numFruit
  }, 0)

  console.log(sum)
  console.log('End')
}

이렇게 하면 원하는 결과 값은 나오지만, reduce 함수를 await 하고, 다시 콜백에 들어가서 accumulator를 await하고, 다시 getNumFruit를 await 하고... 실제로 돌려보면 소요 시간이 꽤 걸리는 것을 콘솔 창에서 확인할 수 있다. 이유는 reduceLoop에서 promisedSum을 매번 loop을 돌 때마다 기다려주기 때문에 이 과정에서 꽤 많은 시간이 소요된다고 한다.

 

따라서 아래와 같이 코드를 수정해주면 심플하고, 가독성도 좋고, 속도도 빠른 코드를 만들 수 있다.

const reduceLoop = async _ => {
  console.log('Start')

  const promises = fruitsToGet.map(getNumFruit)
  const numFruits = await Promise.all(promises)
  const sum = numFruits.reduce((sum, fruit) => sum + fruit)

  console.log(sum)
  console.log('End')
}

 

1. map을 사용하여 await 할 promise들을 배열 형태로 받아준다.

2. Promise.all을 사용하여 위 promise의 결과 값을 배열로 받아준다.

3. reduce를 사용해 합을 구해준다.

 

내 코드에서는 reduce문안에서 async를 쓰다보니, accumulator를 await해주는 과정에서 시간이 오래걸리는 것 같아 속도가 얼마나 줄어들지 궁금하기도하고 속도를 줄여보고자 이 글을 읽고 코드를 변경하려고 했다. 그런데 라이브러리에서 가져온 함수를 await하는 과정에서 자꾸 Error: spawn ENOENT라는 에러가 떴다. 아직 방도는 찾지 못해 코드는 변경하지 않았는데 다시 시도해봐야할 것 같다. 대체 뭐가 문제일까? 기존의 코드로는 잘돌아가는데...