본문 바로가기
IT/JavaScript & TypeScript

[JavaScript/TypeScript] 반복문 안에 있는 비동기 처리 방법

by 저당단 2024. 5. 10.

프로젝트 중 반복문을 돌면서 비동기 요청을 해야 하는 상황이 있어서 찾아보았습니다.

타입을 작성하는 게 이해가 쉬울 것 같아서 TypeScript로 작성하였습니다.

 

 

1. forEach

무조건 비동기적으로 처리됨.

const nums: number[] = [1, 2, 3];

nums.forEach(num => {
    axios.get(`127.0.0.1:8080/${num}`)
    .then(() => console.log(num));
});

요청 순서는 1, 2, 3의 순서지만 비동기적이므로 응답이 먼저 오는 순서대로 로그가 찍힌다.

async나 await로도 동기 처리가 되지 않는다.

 

 

 

2. for 또는 for-of

async와 await을 사용함으로써 순서대로 처리가 가능함. 아래 코드에서는 for-of 문 사용.

※ await은 Promise가 해결(아래 코드에서는 result.push()가 일어날 때) 될 때까지 기다리기 위해 사용함.

Promise 앞에 await이 있다면 Promise가 반환하는 실제 값을 받을 수 있음

const nums: number[] = [1, 2, 3];

const getApiResults = async (): Promise<number[]> => {
    results: number[] = [];
    
    for (const num of nums) {
        await axios.get(`127.0.0.1:8080/${num}`)
        .then(() => results.push(num));
    }

    return results;
}

그러나 각각의 요청에 대한 응답이 올 때까지 순서대로 모두 기다리기 때문에 동기를 맞추기 위해 속도가 느리다.

 

 

 

3. map()과 Promise.all()

map은 응답이 온 순서가 아닌 요청을 보낸 순서대로 반환하며, Promise.all()은 배열의 모든 Promise를 기다린다.

const nums: number[] = [1, 2, 3];

const getApiResults = async (): Promise<number> => {
    results: number[] = [];

    const promises: Promises<void>[] = nums.map(async num => {
        await axios.get(`127.0.0.1:8080/${num}`)
            .then(() => results.push(num));
    });

    await Promise.all(promises);

    return results;
}

즉, 처리 순서는 요청을 보낸 순서를 따르지 않지만 처리 결과는 순서대로 반환한다.

꼭 순서대로 처리해야 하는 상황이라면 2번째 방법을 이용하고, 그렇지 않다면 속도 이점을 위해 되도록 이 방법을 이용하자.

 

(+)

ES2020에 추가된 Promise.allSetteled() 를 이용하면 배열 안의 Promise들 중 일부가 Reject 되더라도 다른 Promise들은 계속 이행한다. (Promise.all은 하나라도 실패할 경우 모두 취소됨)