본문으로 바로가기

반복문 병렬 처리

category 개발 공부/Web (웹) 2021. 1. 8. 09:58
작성 날짜 수정 날짜
2021-01-07 2021-01-08

이번에 예전에 공부하면서 작성한 코드를 리팩토링할 일이 생겼다.
리팩토링 중 for문으로 범벅되어 있는 것을 이번에 forEach로 바꿔보려고 시도를 했다.

수정 전

const files = [
      { id: 1, name: 'first.txt' },
      { id: 2, name: 'second.txt' },
      { id: 3, name: 'third.txt' },
  ]
  
  let fileName, file
  let fileList = []
  for (let i = 0; i < files.length; i++) {
      file = await File.create({ files[i].name })
      fileList.push(file.dataValues)
  }
  console.log(fileList)

실제 코드에서 로직 부분만 간추렸다.

수정 후

const files = [
      { id: 1, name: 'first.txt' },
      { id: 2, name: 'second.txt' },
      { id: 3, name: 'third.txt' },
  ]
  
  let fileName, file
  let fileList = []
  files.forEach(async element => {
      file = await File.create({ element.name })
      fileList.push(file.dataValues)
  })
  console.log(fileList)

나는 forEach를 활용해 위와 같이 코드를 작성했다.

기대 결과

[
      { id: 1, fileName: 'first.txt' },
      { id: 2, fileName: 'second.txt' },
      { id: 3, fileName: 'third.txt' }
  ]

나는 이전 코드에서 위와 같은 결과를 원했다.

현실

[]

하지만 실제 결과는 위와 같다.

왜 for문에서는 제대로 돌아가던 코드가 forEach로 전환했을 때 돌아가지 않는 것일까?
왜냐하면 forEach는 반복문의 종료를 기다리지 않기 때문이다. 반복문이 끝나지 않아도 다음 구문을 실행시킨다.
fileList 배열에 push하기 전에 fileList를 console.log로 출력하기 때문에, 빈 배열로 나오는 것이다.

이것을 해결하기 위한 방법은 크게 2가지가 있다. 그것은 순차 처리와 병렬 처리이다.

순차 처리

순차처리는 배열 요소마다 차례대로 비동기 작업을 수행하는 것이다.
로직의 실행 순서가 보장되어야할 때 사용한다.

병렬 처리

배열 요소에 대해서 한꺼번에 비동기 작업을 수행한다.
로직의 실행 순서가 중요하지 않을 때 사용한다.
실행 순서가 상관 없는 경우에 병렬 처리를 하는 것이 유리하다. 왜냐하면 병렬 처리가 속도가 더 빠르기 때문이다.

순차 처리에는 방법이 여러 가지가 있다.

  1. for (기존 방식)
  2. for...of

for...of

const files = [
      { id: 1, name: 'first.txt' },
      { id: 2, name: 'second.txt' },
      { id: 3, name: 'third.txt' },
  ]
  
  let fileName, file
  let fileList = []
  for (let element of files) {
      file = await File.create({ element.name })
      fileList.push(file.dataValues)
  }
  console.log(fileList)

위와 같이 작성하면 제대로 작동하지만, 순차적으로 비동기 작업을 수행하기에 시간이 오래 걸린다.

Promise.allmap을 활용해서 병렬 처리를 구현하면, 성능 면에서 매우 유리하다.

Promise.all + map

const files = [
      { id: 1, name: 'first.txt' },
      { id: 2, name: 'second.txt' },
      { id: 3, name: 'third.txt' },
  ]
  
  let fileName, file
  let fileList = []
  
  const pushFile = async (file) => {
      file = await File.create({ element.name })
      fileList.push(file.dataValues)
  }
  
  const promises = files.map(file => pushFile(file))
  await Promise.all(promises)
  console.log(fileList)

코드 설명을 좀 해보겠다.
일단 기존 반복문에 들어가는 로직으로 이루어진 비동기 함수(pushFile)를 하나 만든다.
그리고 map을 이용해서 해당 promise 함수들을 엮어준다.
그리고 Promise.all을 사용해서 한꺼번에 병렬 처리한다.
for문에서 forEach문으로 바꿨을 때 작동하지 않는다고 당황하지 말고, Promise.all과 map을 사용하자.

참조
MDN map