본문 바로가기
Frontend/JavaScript

JavaScript : 비동기를 처리하는 방법 (Promise)

by 코딩쥐 2024. 8. 11.

Promise

콜백함수에대 해 설명하면서 비동기 처리에 사용이 된다고 한 적이 있다. 하지만 콜백함수의 경우에는 콜백함수의 깊이가 깊어질 수록 난독을 발생시키고 코드관리가 어려워진다. 이러한 단점을 보완하기 위해서 ES6부터 Promise를 제공하게 되었다.  

  • new Promise(function(resolve, reject){ 코드 });
    promise 클래스 선언을 하고, 클래스 선언 시에 콜백 함수를 인자로 가지게 되는데 해당 콜백함수에는 매개변수로 resolve와 reject란 매개변수를 갖고 있다. 코드 내에 콜백 함수 인자 resolve를 시행하면, then()을 이용하여 처리 결과값을 받을 수 있고, reject를 시행하면 실패상태가 되며 처리 결과를 then() 혹은 catch()로 받을 수 있다. 

 

then(함수1, 함수2)

then()메서드는 두 개의 콜백 함수를 인수로 받는다. 첫번째 함수는 Promise가 이행(resolve)되었을 때, 다른 하나는 거부(reject) 되었을 때를 위한 콜백 함수이다. then()의 경우 연속적으로 사용할 수 있다. 

 

then을 연속적으로 사용할 때 유의할 점이 있다. Promise다음 then()의 경우에는 resolve와 reject에 따라서 콜백함수가 시행되지만, 그 다음 then()부터는 예외처리가 시행되었기 때문에 정상처리가 되었다고 받아들이게 된다. 따라서 세번째 then()부터는 첫번째 함수가 계속 시행된다.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        const promise = new Promise(function (resolve, reject) {
            console.log("첫번째로 시행한 문장");
            reject("reject");
        }).then(
            // resolve가 시행되었을 때
            function func1(a) {
                console.log(`${a}를 매개변수로 받아서 시행`);
                return("resolve");
            },

            // reject가 시행되었을 때 
            function func2(b) {
                console.log(`${b}를 매개변수로 받아서 시행`);
                return("reject");
            }
            //그 다음 then()부터는 reject도 정상처리 되었다고 받아들여서 첫번째 함수를 시행한다.
        ).then(
            function func1(a){
                console.log(`첫번째 function 시행, 매개변수 ${a}`);
            },
            function func2(b){
                console.log(`두번째 function 시행, 매개변수 ${b}`);
            }
        )

        // 첫번째로 시행한 문장
        // reject를 매개변수로 받아서 시행
        // 첫번째 function 시행, 매개변수 reject

    </script>
</body>

</html>

 

catch()

에러가 발생한 경우 then()을 통해 처리할 수도 있지만, Promise가 거부되면 제어 흐름에서 제일 가까운 rejection 핸들러로 넘어가기 때문에 체인 끝에 .catch()를 사용하여 에러를 처리하는 경우가 많다.

  • .catch()
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        const promise = new Promise(function (resolve, reject) {
            console.log("첫번째로 시행한 문장");
            reject("reject");
        }).then(
            (a) => {
                console.log(`${a}를 매개변수로 받아서 시행`);
            }
        ).catch(
            (b) => {
                console.log(`catch시행 : ${b}`);
            }
        )

        // 첫번째로 시행한 문장
        //catch시행 : reject
    </script>
</body>

</html>

 

all()

all()메서드는 Promise가 담겨 있는 배열 등을 인자로 전달 받아, 모든 Promise를 병렬로 처리한 후 처리 결과를 반환한다. 여러 개의 비동기 작업들을 모두 완료한 후에 로직을 진행하고 싶을 때 사용한다. Promise.all() 배열 내 요소 중 하나라도 reject되면, 시행되지 않는다. 

  • Promise.all([
    new Promise(function(resolve, reject){코드}),
    new Promise(function(resolve, reject){코드}), ...]);

아래 예제의 경우 모든 promise들이 이행되고 난 뒤(마지막 Promise 객체가 3초 뒤에 완료된다) 3초 뒤에 .then()이 시행된다. 확인해보면 배열형식으로 resolve 값이 저장된다. 아래 예제에서 promise 중에 하나라도 이행되지않으면(reject) 시행되지 않는 모습을 볼 수 있다.

 

race()

전달된 Promise들 중 가장 빨리 resolve나 reject된 값을 반환한다.

  • Promise.race([
    new Promise(function(resolve, reject){코드}),
    new Promise(function(resolve, reject){코드}), ...]);

아래의 예제는 두번째 Promise가 가장 빠른 값을 반환하기 때문에, 해당 값인 5가 반환된 것을 볼 수 있다. 

 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        Promise.race([
            new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(1);
                }, 2000);
            }),
            new Promise((resolve, reject) => {
                setTimeout(() => {
                    reject(5);
                }, 500);
            }),
            new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(2);
                }, 3000);
            }),
            new Promise((resolve, reject) => {
                setTimeout(() => {
                    reject(3);
                }, 1000);
            }),
        ]).then(
            a => {
                console.log(`resolve 시 : ${a}`); 
            }
        ).catch(
            b => {
                console.log(`catch 시행 : ${b}`); //catch 시행 : 5

            }
        )

    </script>
</body>

</html>

 

다중 Promise

그렇다면 동기화가 여러번 필요할 때가 있는데 어떻게 해야하는가에 대한 고민이 생긴다. 동기화가 여러번 필요할 경우 안 쪽에 Promise를 사용해서 동기화를 추가로 할 수 있다.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        const promise = new Promise(function (resolve, reject) {
            console.log("첫번째로 시행한 문장");
            reject("reject");
        }).then(
            // resolve가 시행되었을 때
            function func1(a) {
                console.log(`${a}를 매개변수로 받아서 시행`);
                return ("resolve");
            },

            // reject가 시행되었을 때 
            function func2(b) {

                //return값으로 Promise객체 반환
                return new Promise(function (resolve, reject) {
                    setTimeout(function () {
                        console.log("3초뒤 시행");
                        reject(b);
                    }, 3000);
                }).then(
                    // 두번째 Promise가 resolve가 시행되었을 때
                    function func2_1(b) {
                        console.log("func2의 then : resolve일 때");
                    },

                    // 두번째 Promise가 reject가 시행되었을 때
                    function func2_2(b) {
                        console.log("func2의 then : reject일 때")
                    }
                );
            }
        )

        // 첫번째로 시행한 문장
        // 3초뒤 시행
        // func2의 then : reject일 때
        
    </script>
</body>

</html>