async / await 최적화 방법

kangdari·2020년 6월 1일
2

전적사이트 개발을 진행하던 중 async/await로 반복문 비동기 처리를 하던 중 발생한 문제들을 해결하고 이 글을 정리해보았습니다.

async/await ES2017(ES8) 문법입니다. async/await를 사용하면 promise를 더 직관적인 코드 작성이 가능합니다.

Promise를 이용한 비동기 처리

const getUsers = () => {
    const users = getUsersFromApi()
        .then(users => users);
}

Promise를 사용할 경우 Callback을 사용하는 방법보다 깔끔하고 직관적인 코드 작성이 가능합니다. 비동기 작업이 완료되었을 경우 then, catch 메소드를 통해서 결과 및 에러 처리가 가능합니다. 이러한 작업을 async / await를 사용하면 더 간결하게 처리가 가능합니다..

async / await을 이용한 비동기 처리

const getUsers = async () => {
    const users = await getUsersFromApi();
}

async / await을 이용한 반복문 처리

const wait = (ms) => {
    return new Promise(resolve => setTimeout(resolve, ms))
}

const waitLog = async (item) => {
    await wait(500); 
    console.log(`waitLog : ${item}`)
}

waitLog 함수는 setTimeout 함수를 사용해 500ms 동안 작업을 지연 후 로그를 찍는 함수입니다.

forEach를 이용한 반복처리

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를 이용한 반복 처리

반복문이 끝난 뒤 다음 작업을 수행하고 싶다면 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의 갯수가 많아질수록 실행 시간은 길어집니다.

비동기 병렬 처리

mapPromise.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로 설정해둔 상태 - 예외)

reduce()를 활용한 순차적으로 Promise 함수 실행시키기

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(() => {
    // 성공 후 처리
})

참고
참고

0개의 댓글