async/await를 이용해 loop 다루기

kangdari·2020년 5월 27일
3

이 글은 제가 개발 도중 배열 안의 각 item에 대해서 비동기 처리를 하는 도중 문제가 발생하여 다른 포스팅들을 보고 정리하게 되었습니다.

Synchronous loop(동기 루프)

예전에 자주 사용하는 동기 루프입니다.

for (var i=0; i < array.length; i++) {
  var item = array[i];
  // do something with item
}

for문도 좋지만 이후 가독성 및 유지 관리의 문제로 forEach문을 자주 사용합니다.

array.forEach((item) => {
  // do something with item
});

개발을 진행하다 보면 배열안에 item에 대해서 비동기 처리를 해야하는 경우가 발생합니다.
저같은 경우는 배열안의 요소들을 loop돌며 ajax 처리를 해줘야했습니다.

ES2017(ES8)의 async , await 등장으로 이전에 사용하던 callback 및 promise.then을 이용한 처리보다 더 직관적으로 비동기 처리를 할 수 있게 되었습니다.

Asynchronous loops(비동기 루프)

async function processArray(arr){
    arr.forEach(item => {
        await func(item);
    });
}

이 코드는 syntax error가 발생합니다. 왜냐하면 processArray 함수는 async로 선언되었기 때문에 비동기 함수이지만 forEach내 익명 함수는 동기식이기 때문입니다.

결과를 기다리지 않기 (setTimout을 이용한 실행)

async function processArray(arr){
    arr.forEach(async item => {
        await func(item);
    });
}

forEach내 익명함수에 async를 추가해주면 익명 함수도 비동기로 정의 할 수 있습니다.

그러나 forEach는 해당 loop가 종료될 때까지 기다리지 않습니다. 즉, forEach문은 실행된 뒤 loop의 종료를 기다리지 않고 다음 구문을 실행 시킵니다.
간단한 테스트로 확인해보겠습니다. setTimeout을 사용하여 지연 처리했습니다.

function delay() {
    return new Promise(resolve => setTimeout(resolve, 300));
  }
  
  async function delayedLog(item) {
    // notice that we can await a function
    // that returns a promise
    await delay();
    console.log(item);
  }
  async function processArray(array) {
    array.forEach(async (item) => {
      await delayedLog(item);
    })
    console.log('Done!');
  }
  
  processArray([1, 2, 3]);
  // Done
  // 1
  // 2
  // 3

forEach문의 결과를 기다릴 필요가 없으면 상관없지만 좋은 방법은 아닙니다.

for...of문 사용 (

ES2015의 for...of문은 Symbole 타입의 iterator 속성을 이용합니다.

processArray 비동기 함수의 forEach문을 for...of문으로 변경했습니다.

async function processArray(array) {
  for (const item of array) {
    await delayedLog(item);
  }
  console.log('Done!');
}
// 1
// 2
// 3
// Done

예상했던 결과대로 출력되었습니다. 그러나 현재 코드는 비동기 처리를 하나씩 처리하고 있습니다. 때문에 배열의 크기가 클 경우 실행 시간도 길어집니다.

Promise.all을 이용한 병렬 처리

Promise.all을 이용하여 비동기 처리를 병렬로 실행하도록 수정하겠습니다.

이전에 각 비동기 처리를 위한 Promise를 반환하는 function을 map 메서드로 묶어 Promise 배열을 반환하고 이를 Promise.all을 통해서 한 번에 처리하도록 만듭니다.

  async function processArray(array) {
    // map array to promises
    const promises = array.map(delayedLog);
    // wait until all promises are resolved
    await Promise.all(promises);
    console.log('Done!');
  }

위 비동기 처리 방법는 각 비동기 처리의 delay 시점에 따라 순서가 바뀔 수 있습니다.

실제로 위 방법을 제 프로젝트에 반영해 보았지만 Promise.all을 이용한 병렬 처리의 결괏값은 순차적으로 병렬 처리가 되지 않았습니다. 결과의 순서가 상관없을 경우에는 신경 쓰지 않아도 되지만 비동기 처리의 결과의 순서가 중요할 경우에는 코드의 정정이 필요하다고 생각됩니다.

0개의 댓글