전적사이트 개발을 진행하던 중 async/await로 반복문 비동기 처리를 하던 중 발생한 문제들을 해결하고 이 글을 정리해보았습니다.
async/await ES2017(ES8) 문법입니다. async/await를 사용하면 promise를 더 직관적인 코드 작성이 가능합니다.
const getUsers = () => {
const users = getUsersFromApi()
.then(users => users);
}
Promise를 사용할 경우 Callback을 사용하는 방법보다 깔끔하고 직관적인 코드 작성이 가능합니다. 비동기 작업이 완료되었을 경우 then, catch 메소드를 통해서 결과 및 에러 처리가 가능합니다. 이러한 작업을 async / await를 사용하면 더 간결하게 처리가 가능합니다..
const getUsers = async () => {
const users = await getUsersFromApi();
}
const wait = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms))
}
const waitLog = async (item) => {
await wait(500);
console.log(`waitLog : ${item}`)
}
waitLog 함수는 setTimeout 함수를 사용해 500ms 동안 작업을 지연 후 로그를 찍는 함수입니다.
const AsyncAwaitByForEach = async () => {
[1,2,3].forEach(async item => await waitLog(item));
console.log('end')
}
AsyncAwaitByForEach()
forEach문을 사용하여 [1,2,3]
배열을 반복시켜 비동기 처리를 요청했습니다.
이 경우 콘솔창에 1 2 3 end
로 찍힐 것으로 예상되지만... 실제 실행 결과는 다릅니다.
예상과는 다르게 end가 먼저 실행되고 1 2 3이 출력됩니다. forEach 반복문 전체가 종료되는 것을 기다려주지 않기때문입니다. (map도 마찬가지)
반복문이 끝난 뒤 다음 작업을 수행하고 싶다면 for...of
를 사용합니다.
const AsyncAwaitByForEach = async () => {
console.time('time')
const arr = [1,2,3];
for(const item of arr){
await waitLog(item)
}
console.log('end')
console.timeEnd('time')
}
AsyncAwaitByForEach()
기대했던대로 1 2 3 end
순으로 출력되었습니다.
for...of
를 사용하면 순서를 보장하면서 비동기 처리를 할 수 있지만 반복문의 각 item의 작업이 끝나기 전까지는 다음 item의 작업이 처리되지 않기 때문에 수행시간이 길어진다는 단점이 있습니다. (직렬 처리)
async / await을 사용하여 비동기 작업을 배열로 처리하는 경우 순서가 보장되어야 하는 상황이 아니라면 병렬 처리를 통해 최적화가 가능합니다.
for...of
를 사용한 경우 실행 시간이 1.554s 였습니다. 한번의 비동기 처리가 500ms 걸리도록 설정되어 있고 3개의 item 반복 측정한 결과입니다. 즉, item의 갯수가 많아질수록 실행 시간은 길어집니다.
map
과 Promise.all
을 사용하면 병렬 처리가 가능합니다. 우선 map
을 사용하여 Promise를 반환하도록 promise 배열을 만들고 이를 Promise.all을 통해 한번에 처리하게 하면 병렬 처리가 가능합니다.
const AsyncAwaitByForOfParallel = async () => {
console.time("time");
const arrays = [1, 2, 3];
const arrayPromises = arrays.map(waitLog);
await Promise.all(arrayPromises);
console.log('end');
console.timeEnd("time");
}
AsyncAwaitByForOfParallel()
비동기 병렬 처리를 통해서 실행 시간이 줄어든 것을 확인할 수 있습니다. 하지만 이 방법은
순서를 보장하지 않습니다. 각 item의 비동기 처리 시간이 다를 수 있기 때문입니다.
(예제는 500ms로 설정해둔 상태 - 예외)
const arr = [1,2,3];
const wait = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms))
}
const waitLog = async (item) => {
await wait(500);
console.log(`${item}`)
}
const promArr = arr.map(item => waitLog(item))
Promise.all(promArr)
.then(res => {
// 성공 후 처리
})
순서를 보장하지 않는 병렬 처리 방식입니다.
reduce를 사용하여 순서를 보장하도록 수정하겠습니다.
arr.reduce((preProm, item) => {
return preProm.then(() => waitLog(item))
}, Promise.resolve())
.then(() => {
// 성공 후 처리
})