본문 바로가기
javascript

[javascript] promise 객체

by 옹알이옹 2023. 8. 4.

 

callback 지옥

javascript를 사용해본 분이라면 callback에 매우 익숙할 것이다.
많은 js 함수가 콜백 함수를 인자로 받아 사용하며, 콜백을 통해 비동기 함수의 실행 순서를 컨트롤 할 수 있다.
하지만 컨트롤 할 비동기 함수가 많아지면 아래와 같은 상황이 벌어지는데 이것이 콜백지옥이다.callback 예시

 

 콜백 예시

    // 첫번째 비동기 함수
    function async1() {
        setTimeout(() => {
            console.log("첫번째 비동기 함수");      
        },400);
    }

    // 두번째 비동기 함수
    function async2() {
        setTimeout(() => {
            console.log("두번째 비동기 함수");      
        },300);
    }

    // 동기 함수 1
    function test1() {
        console.log("동기함수1");
    }
 
위의 상황에서 async1, async2, test1() 순서대로 실행을 시키고 싶지만 실제로는 그렇게 실행이 되지 않는다.

결과

이미지 없음

- 비동기 함수의 특성에 따라 액션이 완료되는 시점에 console에 로그가 찍히게 된다. 현재는 setTimeout을 사용하여
해당 함수가 언제 끝나는지 예상할 수는 있지만, api통신을 하는 fetch등을 사용할 때는 해당 함수가 어느 시점에 완료될지
절대 예상할 수 없다.

 

콜백 함수를 사용하여 위의 문제 해결

 
    function test2() {
        console.log("동기함수2");
    }

    function async3(callback) {
        setTimeout(() => {
            console.log("첫번째로 실행 시키고 싶어");      
            callback();
        },300);
    }

    function async4(callback) {
        setTimeout(() => {
            console.log("두번째로 실행 시키고 싶어");
            callback();
        },200);
    }

   // ========== 함수 호출 =============
    async3(function () {
        async4(test2);
    })
비동기 함수 안에 다음 실행 시키고 싶은 함수를 콜백으로 전달하여, 비동기 함수 안에서 다른 비동기 함수를 
실행하여 비동기 함수 간에 실행 순서를 컨트롤할 수 있다.
하지만 보기와 같이 단계가 많아지면 기하급수적으로 함수 자체와 호출 부분이 복잡해진다.

✅ 해당 문제를 해결하기 위해 등장한 것이 promise 객체이다


 promise 객체 

비동기 함수를 다르는 객체로 위에 나온 문제등을 해결할 수 있다. 간단한 사용법은 아래와 같다.
  • 비동기 함수를 promise 객체로 감싼다.
  • promise 객체의 resolve안에 사용할 값을 저장한다.
  • 이후 promise 객체를 사용하기 위해 then절 또는 async/await를 사용한다.

 예제

 
    function test3() {
        console.log("동기함수3");
    }

    function promise1(number) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                const sum = number + 3;
                console.log("첫번째로 실행 시키고 싶어");      
                resolve(sum);
            },300);
        });
       
    }

    function promise2(number) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log("두번째로 실행 시키고 싶어");
                const sum = number + 1;
                resolve(sum);
            },200);
        });
    }
  • resolve : promise객체의 비동기 함수가 정상적으로 실행 완료가 되면 resolve(보통 변수명을 resolve라 지정, 달라도 됨) 라는 변수에 저장한 값을 꺼내올 수 있다.
  • reject : promise 객체의 비동기 함수가 실패하는 상황을 지정하여 값을 넣어준 뒤, 실제 실패 상황에서 catch등을 통해 값을 가져올 수 있다.
promise 객체를 생성하는 방법을 보았으니, 사용하는 방법도 알아보자

 

promise 객체 사용 방법

 

 1. then절

 
    promise1(2)
        .then((result) => {
            console.log(result)
            return promise2(3)
        }).then(result => {
            console.log(result);
            test3();
        });
 

결과

이미지 없음

  • promise 객체를 리턴하는 함수에 인자를 넘긴 뒤 호출하여 promise 객체를 리턴 받는다.
  • 이후 promise객체의 then 함수를 사용하여 실행할 콜백 함수를 정의하여 사용한다.
  • then절은 체이닝이 가능하여 위처럼 return then을 통해 이어서 사용할 수 있다.
  • 가장 중요한 것은 resolve안에 저장한 값을 then의 매개변수를 통해 전달받을 수 있다.

 

 2. async/await 사용

   
    async function promiseTest() {
        const result1 = await promise1(3);
        const result2 = await promise2(2);
        test3();    

        console.log("result1 : ", result1);
        console.log("result2 : ", result2);
    }

    promiseTest();

결과

이미지 없음

  • promise 객체를 사용하는 곳을 함수로 감싸준 뒤 해당 함수 앞에 async 키워드를 붙여준다.
  • promise 객체 앞에 await 키워드를 붙인 뒤(promise1() 함수는 promise 객체를 리턴) 호출한다.
  • then의 매개변수로 resolve의 값을 받았던 것과 동일하게, 리턴 값으로 resolve에 저장된 값을 받는다.
  • then절에 비해 코드가 깔끔하고 간결하다.
필자는 promise를 공부 전 모든 비동기 함수에 await async를 사용하면 동기식으로 처리할 수 있다고 착각하고 있었다.
await async는 promise 객체에 대해서 사용할 수 있는 것을 명심하자

 

 reject

promise로 감싼 비동기 함수가 실패한 상황을 정의하고, resolve 변수에 값을 넣어준 뒤 실제 실패 상황에서 사용한다.
   
    function promiseReject(a,b) {
        return new Promise((resolve, reject) => {
           
            setTimeout(() => {
                if (a == b) {
                    reject("두 값이 같으면 안됨");
                } else {
                    resolve(a * b);
                }
            }, 600);
        });
    }

    // 함수 호출
    promiseReject(2, 2)
        .then((result) => {
            console.log(result);
        }).catch(err => {
            console.error(err);
        })
 

결과

이미지 없음

  • 비동기함에서 전달받은 두 값이 동일하면 reject에 메시지를 넣어준다.
  • 실제 호출부에서 동일한 값을 넣게 되면 reject 값이 세팅되며, 에러가 발생할 시 실행 되는 catch절에서 확인 가능함

 활용법

 

상황 : 이미지 파일의 크기를 가져오려는 상황에서 이미지 객체의 onload 가 비동기로 실행되어 시점을 잡을 수가 없었다.

function fnImageSize(filePath) {

    const img = new Image();

    img.src = "https://test.or.kr" + filePath;

    function promiseSize(){
        return new Promise((resolve,reject)=>{

            img.onload = function () {
                resolve({
                    "width":this.width,
                    "height":this.height
                });
            }
            img.onerror = function(){
                reject("이미지 로드 실패");
            }
        });
    }

    return promiseSize().then(result=>{

       return result;
    }).catch(err=>{
        console.error(err);
    });

}
반응형