본문 바로가기

Front End/Javascript

[JavaScript] Promise란?

“A promise is an object that may produce a single value some time in the future”
Promise는 미래에 단일 값을 생성할 수 있는 객체입니다.

 

프로미스는 자바스크립트 비동기 처리에 사용되는 객체입니다. 
여기서 자바스크립트의 비동기 처리란 ‘특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성’을 의미합니다. 프로미스(Promise)를 이용하면 비동기 처리의 결과 값을 처리하는 콜백 함수를 비동기 함수로부터 분리할 수 있습니다. 

 

일단은 Promise 객체를 만들어 보도록하겠습니다.

함수를 비동기 함수로 만들면 됩니다. 두가지 방법이 있습니다. 

 

첫번째는 함수명 앞에 "async" 예약어를 붙이는 겁니다.

두번째는 Promise 객체를 리턴하는 방법입니다.

 

Promise객체를 사용하는 방벙은 다음과 같습니다.

 [object Promise]
.then((result)=>{
      // 성공시 처리할 코드 result에 처리 결과가 넘어옵니다.
})
.catch((error)=>{
      // 실패시 처리할 코드 error에 error 객체가 넘어옵니다.
})
.finally(() => {
      // 성공/실패에 상관없이  항상 실행할 내용을 처리합니다.
 })

 

비동기식 함수를 3가지로 만들어 보고 실행해 보도록 하겠습니다.

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>비 동기 함수 만들기</title>
    <script>
        async function helloAsync() {
            return "hello Async";
        }
       
        console.log(helloAsync());
        helloAsync()
        .then((res) => {
            console.log(res);
        });


        function divAsync(a, b){
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    if(b!==0){
                        result = a / b;
                        resolve(`${a} / ${b} = ${result}`);
                    }else{
                        reject('0으로 나눌수 없습니다.');
                    }
                }, 1000);
            });
        }
       
        console.log(divAsync(15, 3));
        divAsync(18, 6)
        .then((res) => {
            console.log(res);
        });

        const addAsync = (a, b) =>{
            return new Promise((resolve, reject) => {
                try{
                    setTimeout(() => {
                       const res = a + b;
                       resolve(`${a} + ${b} = ${res}`);
                    }, 2000);
                }catch(err){
                    reject(err);
                }
            });
        }

        console.log(addAsync(15, 3));
        addAsync(15, 3)
        .then((res) => {
            console.log(res);
        });
    </script>
</head>
<body>    
</body>
</html>

 

실행 결과 입니다.

모두 함수가 Promise 객체를 리턴 합니다.

 

다음의 예제에서 처럼 Promise를 이용하면 콜백 지옥(callback hell)에서 "탈출"할 수 있습니다.

 

다음은 지정 값의 두배를 알려주는 함수를 만들어 보았습니다. 하나는 콜백 함수를 이용하였고 다른 하나는 Promise객체를 이용하였습니다. 

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 사용</title>
    <script>
        const doubleValue1 = (n, callback) => {
            let value = n * 2;
            console.log(`n = ${value}`);
            if (callback) {
                callback(value);
            }
        }

        // callback 사용
        doubleValue1(3, (n) => {
            doubleValue1(n, (n) => {
                doubleValue1(n, (n) => {
                    doubleValue1(n, (n) => {
                        doubleValue1(n, (n) => {
                            console.log("이제 그만~~~~");
                        });
                    });
                });
            });
        });

        const doubleValue2 = (n) => new Promise((resolve, reject) => {
            let value = n * 2;
            console.log(`n = ${value}`);
            resolve(value);
        });

        // Promis 사용
        doubleValue2(3)
            .then((n) => doubleValue2(n))
            .then((n) => doubleValue2(n))
            .then((n) => doubleValue2(n))
            .then((n) => doubleValue2(n))
            .finally(() => console.log("이제 그만~~~~"));
    </script>
</head>

<body>
</body>

</html>

 

실행결과 입니다.

 

n의 m승을 구하는 함수도 만들어 보았습니다.

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 사용</title>
    <script>
        const square1 = (n, m, callback) => {
            let value = Math.pow(n, m);
            console.log(`${n}${m}승은 = ${value}`);
            if (callback) {
                callback(n, ++m);
            }
        }

        // callback 사용
        square1(2, 0, (n, m) => {
            square1(n, m, (n, m) => {
                square1(n, m, (n, m) => {
                    square1(n, m, (n, m) => {
                        square1(n, m, (n, m) => {
                            square1(n, m, (n, m) => {
                                console.log("-".repeat(20));
                            });
                        });
                    });
                });
            });
        });

        const square2 = (n, m) => new Promise((resolve, reject) => {
            let value = Math.pow(n, m);
            console.log(`${n}${m}승은 = ${value}`);
            resolve([n, ++m]);
        });

        // Promis 사용  
        square2(
2, 0)
            .then((n) => square2(n[0], n[1]))
            .then((n) => square2(n[0], n[1]))
            .then((n) => square2(n[0], n[1]))
            .then((n) => square2(n[0], n[1]))
            .then((n) => square2(n[0], n[1]))
            .finally(() => console.log("-".repeat(20)));
    </script>
</head>

<body>
</body>

</html>

 

실행결과 입니다.

 

위의 두 예제를 보면 Promise를 사용해야 하는 이유를 아시겠죠!!!

 

Promise 3가지 상태

프로미스는 비동기 작업을 진행 단계에 따라 3가지 상태로 나누어 관리합니다.
대기 중인 프로미스는 값과 함께 이행(성공)할 수도, 어떤 이유로 인해 거부(실패)될 수도 있습니다.

 

  • 대기(Pending): 작업을 아직 완료하지 않음
  • 이행(Fulfilled): 작업을 성공적으로 완료함
  • 거부(Rejected): 작업이 모종의 이유로 실패함

이미지 출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

 

첫번째 예제의 콘솔창을 확인해 보면 Promise State 값이 "fulfilled" 임을 확인 가능합니다.

 

Promise 생성자

Promise 객체를 new 키워드로 생성하면 생성자에 전달되는 비동기 작업을 수행하게 됩니다. 이렇게 생성된 Promise는 상태를 가지게 됩니다. Pending(대기), Fulfilled(이행), Reject(거부) 입니다.

 

  • 비동기 작업이 수행 중이라면 '<pending>' 상태를 가진 Promise가 반환 됩니다.
  • 비동기 작업이 정상적으로 성공해서 resolve 함수를 호출했다면 '<resolve>' 상태를 가진 Promise가 반환 됩니다.
  • 비동기 작업이 모종의 이유로 실패해서 reject 함수를 호출했다면 '<rejected> 상태를 가진 Promise가 반환 됩니다.
              new Promise((resolve, reject) => {
                    try{
                         // 비동기 작업 수행
                         // ..

                         /* 비동기 작업이 성공한 경우 */
                        resolve("Success!!");
                   }catch(e){
                        /* 비동기 작업이 실패한 경우 */
                        reject("Error!!");
                   }
            });

 

이러한 Promise는 이행(성공)이나 거부(실패)될 때, 프로미스의 then 메소드에 의해 대기열(큐)에 추가된 처리기들이 호출됩니다. 성공시 then()이 호출되고 실패시 catch()가 호출되며 성공 실패에 상관없이 마지막에 finally()가 호출됩니다.

 


Promise객체
.then((result)=>{
      // 성공시 처리할 코드
      //  result에 처리 결과가 넘어옵니다. 변수명은 어떤것을 써도 상관없습니다.
      처리과정....

      // 다음 단계로 값을 넘깁니다.
      return data; 
})
.then((data)=>{
      // 2단계 성공시 처리할 코드
      처리과정....

      // 다음 단계로 값을 넘깁니다.
      return data; 
})
.
.
.
.
.then((data)=>{
      // n단계 성공시 처리할 코드
      작업이 완료된 내용을 처리 합니다.
})
.catch((error)=>{
      // 실패시 처리할 코드 error에 error 객체가 넘어옵니다.
})
.finally(() => {
      // 성공/실패에 상관없이  항상 실행할 내용을 처리합니다.
 })

 

Promise 처리 방법입니다.

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 사용</title>
    <script>
        const myPromise = (status) => {
            return new Promise((resolve, reject) => {
                // 비동기 작업 수행
                // ..

                if (status) {
                    /* 비동기 작업이 성공한 경우 */
                    resolve("Success!!");
                } else {
                    /* 비동기 작업이 실패한 경우 */
                    reject("Error!!");
                }
            });
        };

        // 성공예
        myPromise(true)
            .then((res) => {
                console.log(res); // Success!!
            })
            .catch((err) => {
                console.log(err); // Error!!
            })
            .finally(() => {
                console.log('언제나 실행됨!!!'); // 언제나 실행됨
            });

        // 실패 예
        myPromise(false)
            .then((res) => {
                console.log(res); // Success!!
            })
            .catch((err) => {
                console.log(err); // Error!!
            })
            .finally(() => {
                console.log('언제나 실행됨~~~'); // 언제나 실행됨
            });        
    </script>
</head>

<body>
</body>

</html>

 

 

 

만약, resolve 함수에 여러개의 값을 전달하고 싶은 경우에는 어떻게 해야 할까요? Promise의 resolve 함수는 단일 값을 반환하도록 설계되어 있기 때문에 여러 개의 값을 반환하기 위해서는 객체 또는 배열로 만들어 전달해야 합니다.

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 사용</title>
    <script>
        // 만약, resolve 함수에 두 개의 인자를 전달하고 싶은 경우
        // Promise의 resolve 함수는 단일 값을 반환하도록 설계되어 있다.
        // 때문에, 여러 개의 값을 반환하기 위해서는 객체 또는 배열을 사용해야 한다.
        const myPromise = new Promise((resolve, reject) => {
            // 비동기 작업 수행
            // ..
            resolve(["Success", 42, true]); // 배열을 사용하여 세개의 값을 반환
        });

       // 사용예
        myPromise
            .then((response) => {
                let [data, ...remain] = response;
                console.log(`첫번째 데이터 처리 : ${data}`); // "Success"
                return remain;
            })
            .then((response) => {
                let [data, ...remain] = response;
                console.log(`두번째 데이터 처리 : ${data}`); //  42
                return remain;
            })
            .then((response) => {
                let [data, ...remain] = response;
                console.log(`마지막 데이터 처리 : ${data}`); // true
            })
            .catch((error) => {
                console.log(error);
            })
            .finally(() => {
                console.log('모든 작업이 완료되었습니다.'); // 언제나 실행됨
            });            
    </script>
</head>

<body>
</body>

</html>

 

 

양수인지를 판단하는 비동기 함수를 만들어서 처리해 보았습니다.

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 사용</title>
    <script>
        // callback 함수를 사용하여 비동기 실행
        function isPositive(data, resolve, reject) {
            setTimeout(() => {
                if (isNaN(data)) {
                    reject(`"${data}"은 숫자가 아닙니다.`);
                } else {
                    if (data > 0) {
                        resolve(`"${data}"은 양수입니다.`);       // 성공 -> resolve
                    } else {
                        reject(`"${data}"은 0 또는 음수입니다.`);   // 실패 -> reject
                    }
                }
            }, 1000);
        }

        // 함수 호출 : 성공
        isPositive(100,
            (response) => {
                console.log("성공 : " + response);
            },
            (error) => {
                console.log("실패 : " + error);
            });
        // 함수 호출 : 실패
        isPositive(-56,
            (response) => {
                console.log("성공 : " + response);
            },
            (error) => {
                console.log("실패 : " + error);
            });
        // 함수 호출 : 실패
        isPositive('2024년',
            (response) => {
                console.log("성공 : " + response);
            },
            (error) => {
                console.log("실패 : " + error);
            });


        // Promise를 사용 비동기 실행  
        function isPositive2(data) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    if (isNaN(data)) {
                        reject(`"${data}"은 숫자가 아닙니다.`);
                    } else {
                        if (data > 0) {
                            resolve(`"${data}"은 양수입니다.`);       // 성공 -> resolve
                        } else {
                            reject(`"${data}"은 0 또는 음수입니다.`);   // 실패 -> reject
                        }
                    }
                }, 1000);
            });
        }


        // 함수 호출 : 성공
        isPositive2(100)
            .then((response) => {
                console.log("성공 : " + response);
            })
            .catch((error) => {
                console.log("실패 : " + error);
            });

        // 함수 호출 : 실패
        isPositive2(-77)
            .then((response) => {
                console.log("성공 : " + response);
            })
            .catch((error) => {
                console.log("실패 : " + error);
            });
           
        // 함수 호출 : 실패
        isPositive2('2002년 월드컵')
            .then((response) => {
                console.log("성공 : " + response);
            })
            .catch((error) => {
                console.log("실패 : " + error);
            });
    </script>
</head>

<body>
</body>

</html>

 

 

 

Promise 메소드

  • romise 실행 관련 함수: all(), any(), race()
  • Promise 결과 반환 함수: resolve(), reject()
  • Promise 결과 처리 함수(콜백): then(), catch(), finally()

Promise.all(iterable)
- iterable 내의 모든 프로미스를 한 번에 실행하고, 모든 프로미스가 이행할 때까지 대기 합니다.
- 즉, 모든 Promise가 종료될 때까지 대기합니다. 

- 모두 성공시 then 함수에 결과값이 배열로 전달 됩니다.
- Promise 중 하나라도 reject() 시 catch 구문이 실행 됩니다.

Promise.any(iterable)
- iterable 내의 모든 프로미스를 한 번에 실행하고, 주어진 프로미스 중 하나라도 이행하는 순간 then()이 호출 됩니다.
- 즉, 실행된 Promise 중에 하나라도 then()이 호출될 때 까지 대기 합니다.
- 모든 Promise가 reject() 시에만 catch 구문이 실행 됩니다.

- 하나라도 resolve()시에 then 구문이 실행 됩니다.
- 하나라도 먼저 resolve() 된다면 then 구문이 바로 실행 됩니다.

- reject() 여러 개 호출되더라도 then()이 호출될 때 까지 대기 합니다.

Promise.race(iterable)
- iterable 내의 모든 프로미스를 한 번에 실행하고, 주어진 프로미스 중 하나라도 처리(이행 혹은 거부)될 때까지 대기합니다.
- 제일 먼저, resolve() 혹은 reject()가 호출된 Promise의 결과값을 따릅니다.
- resolve()가 먼저 호출되면, then()을 수행 합니다.
- reject()가 먼저 호출되면, catch()를 수행 합니다.

Promise.reject(reasone)
- 주어진 사유로 거부하는 Promise 객체를 반환  합니다.

Promise.resolve(value)
- 주어진 값으로 이행하는 Promise 객체를 반환  합니다.

Promise.prototype.catch()
- 프로미스에 거부 처리기 콜백을 추가하고, 콜백이 호출될 경우 그 반환값으로 이행하며 
- 호출되지 않을 경우, 즉 이전 프로미스가 이행하는 경우 이행한 값을 그대로 사용해 이행하는 새로운 프로미스를 반환  합니다.

Promise.prototype.then()
- 프로미스에 이행과 거부 처리기 콜백을 추가하고, 콜백이 호출될 경우 그 반환값으로 이행하며
- 호출되지 않을 경우 처리된 값과 상태 그대로 처리되는 새로운 프로미스를 반환 합니다.

Promise.prototype.finally()
- 프로미스의 이행과 거부 여부에 상관없이 처리될 경우 항상 호출되는 처리기 콜백을 추가하고,
- 이행한 값 그대로 이행하는 새로운 프로미스를 반환합니다.        

 

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 사용</title>
    <script>
        // step1 : a * 100
        function step1(a) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    const res = a * 100;
                    resolve(res);
                }, 1000);
            });
        }  
        // step2 : a + 100
        function step2(a) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    const res = a + 100;
                    resolve(res);
                }, 2000);
            });
        }  
        // step3 : a * a
        function step3(a) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    const res = a * a;
                    resolve(res);
                }, 1000);
            });
        }  
 
        // Promise 콜백 함수처럼 사용하기(콜백 지옥)
        step1(5).then((response) => {
            console.log("A Result: ", response); // 5 * 100
            step2(response).then((response) => {
                console.log("B Result: ", response); // 500  + 100
                step3(response).then((response) => {
                    console.log("C Result: ", response); // 600 * 600 = 360000
                });
            });
        });

        // Promise 순차적으로 실행
        // 정상 동작시: step1(resolve) -> step2(resolve) -> step3(resolve)
        // step1 실패시: step1(reject) -> catch(err)
        // step2 실패시: step1(resolve) -> step2(reject) -> catch(err)
        step1(5) // step1 Promise의 then(response)
        .then((response) => {
            console.log("A Result: ", response); // 5 * 100
            return step2(response);
        }) // step2 Promise의 then(response)
        .then((response) => {
            console.log("B Result: ", response); // 500  + 100
            return step3(response);
        }) // step3의 Promise의 then(response)
        .then((response) => {
            console.log("C Result: ", response); // 600 * 600 = 360000
        })
        .catch((error) => {
            console.log("Fail: ", error);
        });
    </script>
</head>
<body>
</body>
</html>

 

위의 두가지 Promise 객체 사용방법입니다. 두번째 방법이 간결하고 쉽습니다.

 

Promise.all() 사용예제 입니다.
 
<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 사용</title>
    <script>
        // step1
        function step1() {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve("1단계 성공!!!");
                }, 1000);
            });
        }  
        // step2
        function step2() {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve("2단계 성공!!!");
                }, 1500);
            });
        }  
        // step3
        function step3() {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve("3단계 성공!!!");
                }, 2000);
            });
        }  

        Promise.all([step1(), step2(), step3()])
        .then((res) => {
            console.log("성공: ", res); // 성공 : ['1단계 성공!!!', '2단계 성공!!!', '3단계 성공!!!']
        })
        .catch((err) => {
            console.log("실패: ", err);
        });
    </script>
</head>
<body>
</body>
</html>

 

 

원격의 API를 호출하는 fetch()함수가 있습니다. fetch() 함수가 Promise객체를 리턴 합니다.

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 사용</title>
    <script>
       let usersPromise = fetch("https://randomuser.me/api/?inc=name,email&results=10");
       console.log(usersPromise);
       usersPromise
       .then((response)=>{
            console.log(`1차로 받은 값 : ${typeof(response)}  : ${response}`)
            console.log(JSON.stringify(response));
            return response.json(); // json 객체로 변환
        })
        .then((json)=>{
           console.log(`2차로 받은 값 : ${typeof(json)}  : ${json}`);
           console.log(JSON.stringify(json))
           let result = "<ul>";
           json.results.forEach(user => {
                result += `<li>${user.name.title} ${user.name.first} ${user.name.last}(${user.email})</li>`;
           });
           result += "<ul>";
           document.querySelector('body').innerHTML = result;  
       })
       .catch((err)=>{
            console.log(err);
       })
       .finally(()=>{
            console.log('작업이 완료되었습니다.');
       });
    </script>
</head>
<body>
</body>
</html>