📢 어렵고 정석적인 개념 설명보다는 저같은 초보자도 이해하기 쉽게 정리하는 것을 원칙으로 포스팅하고 있습니다. 😄

[JavaScript] 여러 개의 Promise 객체를 묶어주는 Promise.all

Promise.all이란?

비동기 처리를 하는 Promise 객체가 여러 개일 경우에 이 비동기 객체들이 모두 처리되었는지를 확인하고 싶을 때가 있을 수 있다. 예를 들어, A, B, C라는 3개의 Promise 객체가 있다고 하자. 각각의 객체는 변수 data에 값을 넣어주는 기능을 갖고 있다. 나는 data에 모든 값들이 채워지면 "data 완성!"이라는 문구를 띄우고 싶다. 이때, 필요한 건 A, B, C라는 비동기 함수들이 모두 resolve 되었는 지를 확인하는 것이다. 이 역할을 해주는 게 바로 Promise.all이다.

 

Promise.all()은 인자로 배열 혹은 순회할 수 있는 iterable한 객체를 받으며, 이 안에 들어간 Promise 객체들의 resolve 혹은 reject의 값을 따져서 then 혹은 catch로 결과값을 반환해 준다. 즉, 여러 개의 Promise 객체들을 하나로 묶어서 최종적인 결과를 발표해 주는 역할을 한다고 보면 된다.

 

Promise.all의 예시

const promise1 = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('promise1');
            resolve();
        }, 1000)
    })
}

const promise2 = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('promise2');
            resolve();
        }, 2000)
    })
}

const promise3 = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('promise3');
            resolve();
        }, 3000)
    })
}

Promise.all([promise1(), promise2(), promise3()]);
    .then(() => {
        console.log('모든 promise 완료');
    })
    .catch((error) => {
        console.log('에러 발생')
    })

다음과 같은 식이 있다고 하자. 여기서 Promise 객체는 총 3개로, 1초 간격으로 콘솔에 메시지를 띄우도록 하였다.

그리고 만약 모든 Promise 객체가 resolve한다면 콘솔에 '모든 promise 완료'라는 메시지가 출력되도록 했다.

 

Promise.all([...]) 부분을 보면, 배열 안에 promise1(), promise2(), promise3()을 넣어 하나로 묶었다.

만약 모두 resolve일 경우에는 then()으로 빠지고, 하나라도 실패하면 실패한 함수 차례에서 catch()의 메시지를 출력한다.

위 예시 코드에서는 무조건 resolve하도록 객체 안에 resolve()를 따로 주었다. 만약에, promise2()의 resolve()를 reject()로 바꿔준다면 promise2()가 실패하게 되고, promise2() 차례에서 '에러 발생'이라는 메시지가 출력될 것이다.

(Promise.all()은 하나라도 실패하면 then() 안의 콜백함수가 실행되지 않는다.)

 

const axios = require('axios');

const asyncRequest1 = () => {
    return axios.get('https://api.example.com/endpoint1');
}

const asyncRequest2 = () => {
    return axios.get('https://api.example.com/endpoint2');
}

const asyncRequest3 = () => {
    return axios.get('https://api.example.com/endpoint3');
}

Promise.all([asyncRequest1(), asyncRequest2(), asyncRequest3()])
    .then(results => {
        console.log('모든 비동기 요청 완료');
        console.log(results); // 여기엔 뭐가 출력될까?
    })
    .catch(error => {
        console.log('에러 발생!');
    })

이번에는 데이터를 받아오는 Axios 라이브러리를 사용한 예시이다. Axios도 기본적으로 비동기 처리이기 때문에 앞에서 예시로 든 Promise 객체와 다를 바가 없다. Promise.all() 안에 배열로 3개의 함수를 묶어주었고, 모두 제대로 실행되면 then()의 콜백함수가 실행된다. 이때, then()의 매개변수인 results는 무엇을 출력할까?

 

then()의 매개변수는 Promise.all()로 묶었던 비동기 함수들의 결과를 모두 합친 값을 나타낸다.

예를 들어, asyncRequest1()의 결과가 [1, 2, 3]이고 asyncRequest2()의 결과가 [4, 5, 6], asyncRequest3()이 [7, 8, 9]라면 then()의 매개변수 results는 이것들을 하나로 합친 [[1, 2, 3], [4, 5, 6], [7, 8, 9]]가 된다.

(순서는 처리 순서에 따라 달라질 수 있다. 만약, asyncRequest1()이 제일 오래 걸리면 [[4, 5, 6], [7, 8, 9], [1, 2, 3]])이 된다.)

 

비동기는 일반 동기식처럼 차례차례 순서대로 실행되는 게 아닐뿐더러, 실행된 비동기들도 제각각으로 놀기 때문에 뭔가 제어하기 어렵다는 인식이 있다. 그렇기 때문에 이들을 통제하기 위해서는 하나로 묶은 다음 "너 성공했어? 통과, 그다음 너 성공했어? 통과.." 라고 물어보는 큰 형님이 있어줘야 한다. 이런 역할이 바로 Promise.all()이고 이것은 async-await과는 별개로 Promise 객체를 제어하는 또 하나의 유용한 방법이다.

 

1. Promise.all()에는 배열 혹은 iterable한 객체가 들어간다. (그냥 여러 개의 비동기 객체가 들어간다고 이해하기)
2. 모두 성공하면 then()이 실행, 하나라도 실패하면 실패한 비동기 객체는 catch()가 실행된다.
3. then()의 매개변수는 하나로 묶인 비동기 객체들의 모든 결과값의 합(배열)이다.
4. 비동기들이 모두 처리됐는지의 여부를 확인하거나 비동기로 반환되는 값들을 하나로 합치고 싶을 때 사용한다.